use synta_codegen::ast::*;
use synta_codegen::{
detect_cycles, generate, generate_c, generate_c_impl, generate_c_with_config, generate_cmake,
generate_meson, generate_with_config, parse, topological_order, CCodeGenConfig, CImplConfig,
CMakeConfig, CodeGenConfig, DeriveMode, MesonConfig, PatternMode,
};
fn gen(module: &Module) -> String {
generate(module).unwrap()
}
#[test]
fn test_to_snake_case() {
let module = Module {
values: vec![],
oid: None,
tagging_mode: None,
imports: vec![],
exports: vec![],
name: "TestModule".to_string(),
definitions: vec![Definition {
name: "TestSeq".to_string(),
ty: Type::Sequence(vec![
SequenceField {
name: "camelCase".to_string(),
ty: Type::Integer(None, Vec::new()),
optional: false,
default: None,
},
SequenceField {
name: "kebab-case".to_string(),
ty: Type::Integer(None, Vec::new()),
optional: false,
default: None,
},
]),
}],
};
let code = gen(&module);
assert!(code.contains("pub camel_case: Integer"));
assert!(code.contains("pub kebab_case: Integer"));
}
#[test]
fn test_generate_simple_sequence() {
let module = Module {
values: vec![],
oid: None,
tagging_mode: None,
imports: vec![],
exports: vec![],
name: "TestModule".to_string(),
definitions: vec![Definition {
name: "SimpleSeq".to_string(),
ty: Type::Sequence(vec![
SequenceField {
name: "field1".to_string(),
ty: Type::Integer(None, Vec::new()),
optional: false,
default: None,
},
SequenceField {
name: "field2".to_string(),
ty: Type::OctetString(None),
optional: false,
default: None,
},
]),
}],
};
let code = gen(&module);
assert!(code.contains("pub struct SimpleSeq"));
assert!(code.contains("pub field1: Integer"));
assert!(code.contains("pub field2: OctetString"));
}
#[test]
fn test_generate_optional_field() {
let module = Module {
values: vec![],
oid: None,
tagging_mode: None,
imports: vec![],
exports: vec![],
name: "TestModule".to_string(),
definitions: vec![Definition {
name: "OptSeq".to_string(),
ty: Type::Sequence(vec![SequenceField {
name: "optional".to_string(),
ty: Type::Integer(None, Vec::new()),
optional: true,
default: None,
}]),
}],
};
let code = gen(&module);
assert!(code.contains("Option<Integer>"));
assert!(code.contains("asn1(optional)"));
}
#[test]
fn test_generate_tagged_field() {
let module = Module {
values: vec![],
oid: None,
tagging_mode: None,
imports: vec![],
exports: vec![],
name: "TestModule".to_string(),
definitions: vec![Definition {
name: "TaggedSeq".to_string(),
ty: Type::Sequence(vec![SequenceField {
name: "tagged".to_string(),
ty: Type::Tagged {
tag: TagInfo {
class: TagClass::ContextSpecific,
number: 0,
tagging: Tagging::Explicit,
},
inner: Box::new(Type::Integer(None, Vec::new())),
},
optional: false,
default: None,
}]),
}],
};
let code = gen(&module);
assert!(code.contains("asn1(tag(0, explicit))"));
}
#[test]
fn test_generate_value_constraint() {
let module = Module {
values: vec![],
oid: None,
tagging_mode: None,
imports: vec![],
exports: vec![],
name: "TestModule".to_string(),
definitions: vec![Definition {
name: "Int32".to_string(),
ty: Type::Integer(
Some(ValueConstraint::Range(Some(-2147483648), Some(2147483647))),
Vec::new(),
),
}],
};
let code = gen(&module);
assert!(code.contains("// Constraint: -2147483648..2147483647"));
assert!(code.contains("pub type Int32 = Integer;"));
}
#[test]
fn test_generate_size_constraint() {
let module = Module {
values: vec![],
oid: None,
tagging_mode: None,
imports: vec![],
exports: vec![],
name: "TestModule".to_string(),
definitions: vec![Definition {
name: "ShortString".to_string(),
ty: Type::OctetString(Some(SizeConstraint::Range(Some(1), Some(255)))),
}],
};
let code = gen(&module);
assert!(code.contains("// Constraint: SIZE (1..255)"));
assert!(code.contains("pub type ShortString = OctetString;"));
}
#[test]
fn test_generate_constrained_range() {
let module = Module {
values: vec![],
oid: None,
tagging_mode: None,
imports: vec![],
exports: vec![],
name: "TestModule".to_string(),
definitions: vec![Definition {
name: "Percentage".to_string(),
ty: Type::Constrained {
base_type: Box::new(Type::Integer(None, Vec::new())),
constraint: Constraint {
spec: ConstraintSpec::Subtype(SubtypeConstraint::ValueRange {
min: ConstraintValue::Integer(0),
max: ConstraintValue::Integer(100),
}),
exception: None,
},
},
}],
};
let code = gen(&module);
assert!(code.contains("/// INTEGER (0..100)"));
assert!(code.contains("pub struct Percentage(u8);"));
assert!(code.contains("pub fn new(value: u8) -> Result<Self, &'static str>"));
assert!(code.contains("(0..=100).contains(&value)"));
assert!(code.contains("must be in range 0..100"));
assert!(code.contains("impl core::convert::TryFrom<Integer> for Percentage"));
}
#[test]
fn test_generate_constrained_single_value() {
let module = Module {
values: vec![],
oid: None,
tagging_mode: None,
imports: vec![],
exports: vec![],
name: "TestModule".to_string(),
definitions: vec![Definition {
name: "FortyTwo".to_string(),
ty: Type::Constrained {
base_type: Box::new(Type::Integer(None, Vec::new())),
constraint: Constraint {
spec: ConstraintSpec::Subtype(SubtypeConstraint::SingleValue(
ConstraintValue::Integer(42),
)),
exception: None,
},
},
}],
};
let code = gen(&module);
assert!(code.contains("/// INTEGER (42)"));
assert!(code.contains("pub struct FortyTwo(u8);"));
assert!(code.contains("value == 42"));
assert!(code.contains("must equal 42"));
}
#[test]
fn test_generate_constrained_union() {
let module = Module {
values: vec![],
oid: None,
tagging_mode: None,
imports: vec![],
exports: vec![],
name: "TestModule".to_string(),
definitions: vec![Definition {
name: "HttpPort".to_string(),
ty: Type::Constrained {
base_type: Box::new(Type::Integer(None, Vec::new())),
constraint: Constraint {
spec: ConstraintSpec::Subtype(SubtypeConstraint::Union(vec![
SubtypeConstraint::SingleValue(ConstraintValue::Integer(80)),
SubtypeConstraint::SingleValue(ConstraintValue::Integer(443)),
SubtypeConstraint::SingleValue(ConstraintValue::Integer(8080)),
])),
exception: None,
},
},
}],
};
let code = gen(&module);
assert!(code.contains("/// INTEGER (80 | 443 | 8080)"));
assert!(code.contains("pub struct HttpPort(i64);"));
assert!(code.contains("value == 80 || value == 443 || value == 8080"));
}
#[test]
fn test_generate_constrained_min_max() {
let module = Module {
values: vec![],
oid: None,
tagging_mode: None,
imports: vec![],
exports: vec![],
name: "TestModule".to_string(),
definitions: vec![Definition {
name: "PositiveInteger".to_string(),
ty: Type::Constrained {
base_type: Box::new(Type::Integer(None, Vec::new())),
constraint: Constraint {
spec: ConstraintSpec::Subtype(SubtypeConstraint::ValueRange {
min: ConstraintValue::Integer(0),
max: ConstraintValue::Max,
}),
exception: None,
},
},
}],
};
let code = gen(&module);
assert!(code.contains("/// INTEGER (0..MAX)"));
assert!(code.contains("pub struct PositiveInteger(i64);"));
assert!(code.contains("(value >= 0)"));
assert!(code.contains("must be in range 0..MAX"));
}
#[test]
fn test_generate_constrained_complement() {
let module = Module {
values: vec![],
oid: None,
tagging_mode: None,
imports: vec![],
exports: vec![],
name: "TestModule".to_string(),
definitions: vec![Definition {
name: "NonZero".to_string(),
ty: Type::Constrained {
base_type: Box::new(Type::Integer(None, Vec::new())),
constraint: Constraint {
spec: ConstraintSpec::Subtype(SubtypeConstraint::Complement(Box::new(
SubtypeConstraint::SingleValue(ConstraintValue::Integer(0)),
))),
exception: None,
},
},
}],
};
let code = gen(&module);
assert!(code.contains("/// INTEGER (ALL EXCEPT 0)"));
assert!(code.contains("pub struct NonZero(i64);"));
assert!(code.contains("!(value == 0)"));
}
#[test]
fn test_generate_constrained_complex_union() {
let module = Module {
values: vec![],
oid: None,
tagging_mode: None,
imports: vec![],
exports: vec![],
name: "TestModule".to_string(),
definitions: vec![Definition {
name: "ValidAge".to_string(),
ty: Type::Constrained {
base_type: Box::new(Type::Integer(None, Vec::new())),
constraint: Constraint {
spec: ConstraintSpec::Subtype(SubtypeConstraint::Union(vec![
SubtypeConstraint::ValueRange {
min: ConstraintValue::Integer(0),
max: ConstraintValue::Integer(17),
},
SubtypeConstraint::ValueRange {
min: ConstraintValue::Integer(18),
max: ConstraintValue::Integer(65),
},
SubtypeConstraint::ValueRange {
min: ConstraintValue::Integer(66),
max: ConstraintValue::Max,
},
])),
exception: None,
},
},
}],
};
let code = gen(&module);
assert!(code.contains("/// INTEGER (0..17 | 18..65 | 66..MAX)"));
assert!(code.contains("pub struct ValidAge(i64);"));
assert!(code.contains("(0..=17).contains(&value)"));
assert!(code.contains("(18..=65).contains(&value)"));
assert!(code.contains("(value >= 66)"));
}
#[test]
fn test_parse_and_generate_with_constraints() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Percentage ::= INTEGER (0..100)
ShortString ::= IA5String (SIZE (1..64))
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(code.contains("pub struct Percentage(u8)"));
assert!(code.contains("pub struct ShortString(IA5String)"));
assert!(code.contains("pub fn new(value:"));
assert!(code.contains("Result<Self, &'static str>"));
}
#[test]
fn test_sequence_of_with_constrained_elements() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
PositiveNumbers ::= SEQUENCE OF INTEGER (1..MAX)
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(code.contains("pub struct PositiveNumbersElement(i64)"));
assert!(code.contains("/// INTEGER (1..MAX)"));
assert!(code.contains("(value >= 1)"));
assert!(code.contains("pub type PositiveNumbers = Vec<PositiveNumbersElement>"));
}
#[test]
fn test_set_of_with_constrained_elements() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
ShortStrings ::= SET OF IA5String (SIZE (1..64))
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(code.contains("pub struct ShortStringsElement(IA5String)"));
assert!(code.contains("/// IA5String (SIZE (1..64))"));
assert!(code.contains("!value.as_str().is_empty() && value.as_str().len() <= 64"));
assert!(code.contains("pub type ShortStrings = SetOf<ShortStringsElement>"));
}
#[test]
fn test_containing_constraint() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
EncodedInteger ::= OCTET STRING (CONTAINING INTEGER)
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(code.contains("/// OctetString (CONTAINING Integer)"));
assert!(code.contains("pub struct EncodedInteger(OctetString)"));
assert!(code.contains("#[cfg(feature = \"validate_containing\")]"));
assert!(code.contains("use synta::der::Decoder;"));
assert!(code.contains("let _decoded: Integer = decoder.decode()"));
assert!(code.contains("invalid Integer in CONTAINING constraint"));
assert!(code.contains("#[cfg(not(feature = \"validate_containing\"))]"));
assert!(code.contains("CONTAINING validation disabled"));
}
#[test]
fn test_containing_with_constrained_type() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
EncodedPercentage ::= OCTET STRING (CONTAINING INTEGER (0..100))
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(code.contains("pub struct EncodedPercentage(OctetString)"));
assert!(code.contains("CONTAINING"));
}
#[test]
fn test_default_integer_values() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Settings ::= SEQUENCE {
port INTEGER DEFAULT 8080,
timeout INTEGER DEFAULT 30
}
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(code.contains("pub struct Settings"));
assert!(code.contains("pub port: Option<Integer>"));
assert!(code.contains("pub timeout: Option<Integer>"));
}
#[test]
fn test_default_boolean_values() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Flags ::= SEQUENCE {
enabled BOOLEAN DEFAULT TRUE,
disabled BOOLEAN DEFAULT FALSE
}
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(code.contains("pub struct Flags"));
assert!(code.contains("pub enabled: Option<Boolean>"));
assert!(code.contains("pub disabled: Option<Boolean>"));
}
#[test]
fn test_default_string_values() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Config ::= SEQUENCE {
name UTF8String,
country UTF8String DEFAULT "US"
}
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(!code.contains("impl Default for Config"));
}
#[test]
fn test_mixed_defaults_and_required() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Person ::= SEQUENCE {
name UTF8String,
age INTEGER DEFAULT 0,
active BOOLEAN DEFAULT TRUE
}
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(!code.contains("impl Default for Person"));
assert!(!code.contains("TODO: Required field without default"));
}
#[test]
fn test_no_defaults_no_impl() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Simple ::= SEQUENCE {
name UTF8String,
age INTEGER
}
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(!code.contains("impl Default for Simple"));
}
#[test]
fn test_combined_size_and_from() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
PinCode ::= IA5String (SIZE (4..6) FROM ("0".."9"))
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(code.contains("pub struct PinCode(IA5String)"));
assert!(code.contains("SIZE") && code.contains("FROM"));
assert!(code.contains("Check SIZE constraint"));
assert!(code.contains("len() >= 4 && value.as_str().len() <= 6"));
assert!(code.contains("length must be in range 4..6"));
assert!(code.contains("Check FROM constraint"));
assert!(code.contains("ch >= '0' && ch <= '9'"));
assert!(code.contains("characters must be from: '0'..'9'"));
}
#[test]
fn test_combined_size_and_alphabet() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Username ::= UTF8String (SIZE (3..20) FROM ("A".."Z" | "a".."z" | "0".."9" | "_"))
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(code.contains("pub struct Username(Utf8String)"));
assert!(code.contains("Check SIZE constraint"));
assert!(code.contains("len() >= 3 && value.as_str().len() <= 20"));
assert!(code.contains("Check FROM constraint"));
assert!(code.contains("ch >= 'A' && ch <= 'Z'"));
assert!(code.contains("ch >= 'a' && ch <= 'z'"));
assert!(code.contains("ch >= '0' && ch <= '9'"));
assert!(code.contains("ch == '_'"));
}
#[test]
fn test_size_only_still_works() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
ShortString ::= IA5String (SIZE (1..64))
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(code.contains("pub struct ShortString(IA5String)"));
assert!(code.contains("!value.as_str().is_empty() && value.as_str().len() <= 64"));
}
#[test]
fn test_from_only_still_works() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
DigitString ::= IA5String (FROM ("0".."9"))
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(code.contains("pub struct DigitString(IA5String)"));
assert!(code.contains("ch >= '0' && ch <= '9'"));
}
#[test]
fn test_parse_exports() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
EXPORTS Type1, Type2, Type3;
Type1 ::= INTEGER
Type2 ::= BOOLEAN
Type3 ::= OCTET STRING
END
"#;
let module = parse(input).unwrap();
assert_eq!(module.exports.len(), 3);
assert!(module.exports.contains(&"Type1".to_string()));
assert!(module.exports.contains(&"Type2".to_string()));
assert!(module.exports.contains(&"Type3".to_string()));
let code = generate(&module).unwrap();
assert!(code.contains("// EXPORTS:"));
assert!(code.contains("// Type1"));
}
#[test]
fn test_parse_imports() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
IMPORTS
Percentage, ShortString
FROM BaseTypes;
User ::= SEQUENCE {
name ShortString,
age Percentage
}
END
"#;
let module = parse(input).unwrap();
assert_eq!(module.imports.len(), 1);
assert_eq!(module.imports[0].module_name, "BaseTypes");
assert_eq!(module.imports[0].symbols.len(), 2);
assert!(module.imports[0]
.symbols
.contains(&"Percentage".to_string()));
assert!(module.imports[0]
.symbols
.contains(&"ShortString".to_string()));
let code = generate(&module).unwrap();
assert!(code.contains("// IMPORTS:"));
assert!(code.contains("FROM BaseTypes"));
}
#[test]
fn test_parse_multiple_imports() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
IMPORTS
Type1, Type2 FROM Module1
Type3 FROM Module2;
Combined ::= SEQUENCE {
field1 Type1,
field2 Type2,
field3 Type3
}
END
"#;
let module = parse(input).unwrap();
assert_eq!(module.imports.len(), 2);
assert_eq!(module.imports[0].module_name, "Module1");
assert_eq!(module.imports[0].symbols.len(), 2);
assert!(module.imports[0].symbols.contains(&"Type1".to_string()));
assert!(module.imports[0].symbols.contains(&"Type2".to_string()));
assert_eq!(module.imports[1].module_name, "Module2");
assert_eq!(module.imports[1].symbols.len(), 1);
assert!(module.imports[1].symbols.contains(&"Type3".to_string()));
}
#[test]
fn test_parse_exports_and_imports() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
EXPORTS User, UserList;
IMPORTS
Percentage, ShortString
FROM BaseTypes;
User ::= SEQUENCE {
name ShortString,
age Percentage
}
UserList ::= SEQUENCE OF User
END
"#;
let module = parse(input).unwrap();
assert_eq!(module.exports.len(), 2);
assert_eq!(module.imports.len(), 1);
let code = generate(&module).unwrap();
assert!(code.contains("// EXPORTS:"));
assert!(code.contains("// User"));
assert!(code.contains("// IMPORTS:"));
assert!(code.contains("FROM BaseTypes"));
}
#[test]
fn test_generate_with_crate_imports() {
let input = r#"
UserModule DEFINITIONS ::= BEGIN
IMPORTS
Percentage, ShortString
FROM BaseTypes;
User ::= SEQUENCE {
name ShortString,
age Percentage
}
END
"#;
let module = parse(input).unwrap();
let config = synta_codegen::CodeGenConfig::with_crate_imports();
let code = synta_codegen::generate_with_config(&module, config).unwrap();
assert!(code.contains("use crate::base_types::"));
assert!(code.contains("{Percentage, ShortString}"));
assert!(code.contains("// IMPORTS:"));
}
#[test]
fn test_generate_with_super_imports() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
IMPORTS
Type1
FROM Module1;
Wrapper ::= SEQUENCE {
field Type1
}
END
"#;
let module = parse(input).unwrap();
let config = synta_codegen::CodeGenConfig::with_super_imports();
let code = synta_codegen::generate_with_config(&module, config).unwrap();
assert!(code.contains("use super::module1::Type1;"));
}
#[test]
fn test_generate_with_custom_prefix() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
IMPORTS
CustomType
FROM CustomModule;
MyType ::= CustomType
END
"#;
let module = parse(input).unwrap();
let config = synta_codegen::CodeGenConfig::with_custom_prefix("my_lib::types");
let code = synta_codegen::generate_with_config(&module, config).unwrap();
assert!(code.contains("use my_lib::types::custom_module::CustomType;"));
}
#[test]
fn test_generate_without_config_no_use_statements() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
IMPORTS
Type1
FROM Module1;
MyType ::= Type1
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(!code.contains("use crate::"));
assert!(!code.contains("use super::"));
assert!(code.contains("// IMPORTS:"));
assert!(code.contains("FROM Module1"));
}
#[test]
fn test_module_name_conversion() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
IMPORTS
Type1 FROM CamelCaseModule
Type2 FROM kebab-case-module
Type3 FROM SCREAMING-CASE-MODULE;
Combined ::= SEQUENCE {
field1 Type1,
field2 Type2,
field3 Type3
}
END
"#;
let module = parse(input).unwrap();
let config = synta_codegen::CodeGenConfig::with_crate_imports();
let code = synta_codegen::generate_with_config(&module, config).unwrap();
assert!(code.contains("use crate::camel_case_module::Type1;"));
assert!(code.contains("use crate::kebab_case_module::Type2;"));
assert!(code.contains("use crate::screaming_case_module::Type3;"));
}
#[test]
fn test_single_import_vs_multiple() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
IMPORTS
Single FROM Module1
Type1, Type2, Type3 FROM Module2;
Test ::= SEQUENCE {
a Single,
b Type1
}
END
"#;
let module = parse(input).unwrap();
let config = synta_codegen::CodeGenConfig::with_crate_imports();
let code = synta_codegen::generate_with_config(&module, config).unwrap();
assert!(code.contains("use crate::module1::Single;"));
assert!(code.contains("use crate::module2::{Type1, Type2, Type3};"));
}
#[test]
fn test_integer_with_named_values() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Protocol ::= INTEGER {tcp(6), udp(17), sctp(132)}
Version ::= INTEGER {v1(0), v2(1), v3(2)}
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(code.contains("pub type Protocol = Integer;"));
assert!(code.contains("pub type Version = Integer;"));
assert!(code.contains("pub const PROTOCOL_TCP: i64 = 6;"));
assert!(code.contains("pub const PROTOCOL_UDP: i64 = 17;"));
assert!(code.contains("pub const PROTOCOL_SCTP: i64 = 132;"));
assert!(code.contains("pub const VERSION_V1: i64 = 0;"));
assert!(code.contains("pub const VERSION_V2: i64 = 1;"));
assert!(code.contains("pub const VERSION_V3: i64 = 2;"));
}
#[test]
fn test_named_values_with_constraint() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
SmallProtocol ::= INTEGER {tcp(6), udp(17)} (0..100)
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(code.contains("pub struct SmallProtocol(u8);"));
assert!(code.contains("pub fn new(value: u8) -> Result<Self, &'static str>"));
assert!(code.contains("impl SmallProtocol {"));
assert!(code.contains("pub const TCP: u8 = 6;"));
assert!(code.contains("pub const UDP: u8 = 17;"));
}
#[test]
fn test_implicit_on_choice_promoted_to_explicit_local() {
let input = r#"
TestModule DEFINITIONS IMPLICIT TAGS ::= BEGIN
MyChoice ::= CHOICE { a INTEGER, b BOOLEAN }
MySeq ::= SEQUENCE {
tagged [0] MyChoice OPTIONAL
}
END
"#;
let module = parse(input).unwrap();
let code = generate_with_config(&module, CodeGenConfig::default()).unwrap();
assert!(
code.contains("tag(0, explicit)"),
"expected explicit tag for CHOICE field, got:\n{code}"
);
}
#[test]
fn test_implicit_on_choice_promoted_via_known_choice_types() {
let input = r#"
TestModule DEFINITIONS IMPLICIT TAGS ::= BEGIN
MySeq ::= SEQUENCE {
tagged [0] ImportedChoice OPTIONAL
}
END
"#;
let module = parse(input).unwrap();
let mut config = CodeGenConfig::default();
config.known_choice_types.insert("ImportedChoice".into());
let code = generate_with_config(&module, config).unwrap();
assert!(
code.contains("tag(0, explicit)"),
"expected explicit tag for known imported CHOICE field, got:\n{code}"
);
}
#[test]
fn test_subtype_definitions() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Percentage ::= INTEGER (0..100)
SmallPercentage ::= Percentage (0..50)
MyPercentage ::= Percentage
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(code.contains("pub struct Percentage(u8);"));
assert!(code.contains("pub fn new(value: u8) -> Result<Self, &'static str>"));
assert!(code.contains("if (0..=100).contains(&value)"));
assert!(code.contains("pub struct SmallPercentage(Percentage);"));
assert!(code.contains("pub fn new(value: Percentage) -> Result<Self, &'static str>"));
assert!(code.contains("let val = value.into_inner();"));
assert!(code.contains("if (0..=50).contains(&val)"));
assert!(code.contains("pub type MyPercentage = Percentage;"));
}
#[test]
fn test_string_subtype_definitions() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Email ::= IA5String (SIZE (5..100))
ShortEmail ::= Email (SIZE (5..50))
UserEmail ::= Email
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(code.contains("pub struct Email(IA5String);"));
assert!(code.contains("pub fn new(value: IA5String) -> Result<Self, &'static str>"));
assert!(code.contains("value.as_str().len() >= 5 && value.as_str().len() <= 100"));
assert!(code.contains("pub struct ShortEmail(Email);"));
assert!(code.contains("pub fn new(value: Email) -> Result<Self, &'static str>"));
assert!(code.contains("value.as_str().len() >= 5 && value.as_str().len() <= 50"));
assert!(code.contains("pub type UserEmail = Email;"));
}
#[test]
fn test_pattern_constraint_placeholder() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Email ::= IA5String (PATTERN "[a-z]+@[a-z]+")
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(code.contains("pub struct Email(IA5String);"));
assert!(code.contains("#[cfg(feature = \"regex\")]"));
assert!(code.contains("use regex::Regex;"));
assert!(code.contains("use once_cell::sync::Lazy;"));
assert!(code.contains("static PATTERN_0"));
assert!(code.contains("Lazy::new(|| Regex::new"));
assert!(code.contains(r#"r"[a-z]+@[a-z]+""#));
assert!(code.contains("if !PATTERN_0.is_match"));
assert!(code.contains("value does not match pattern"));
assert!(code.contains("#[cfg(not(feature = \"regex\"))]"));
assert!(code.contains("Pattern validation disabled"));
}
#[test]
fn test_inner_type_constraint_on_sequence_of() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
PositiveList ::= SEQUENCE OF INTEGER (1..MAX)
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(code.contains("PositiveListElement"));
assert!(code.contains("value >= 1"));
}
#[test]
fn test_complex_union_constraint() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
HttpPort ::= INTEGER (80 | 443 | 8080)
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(code.contains("pub struct HttpPort(i64);"));
assert!(code.contains("value == 80"));
assert!(code.contains("value == 443"));
assert!(code.contains("value == 8080"));
assert!(code.contains("||"));
}
#[test]
fn test_complement_constraint() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
NonZero ::= INTEGER (ALL EXCEPT 0)
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(code.contains("pub struct NonZero(i64);"));
assert!(code.contains("!("));
assert!(code.contains("value == 0"));
}
#[test]
fn test_x509_like_validity() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Time ::= CHOICE {
utc UTCTime,
generalized GeneralizedTime
}
Validity ::= SEQUENCE {
notBefore Time,
notAfter Time
}
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(code.contains("pub enum Time {"));
assert!(code.contains("pub struct Validity {"));
assert!(code.contains("pub not_before: Time"));
assert!(code.contains("pub not_after: Time"));
}
#[test]
fn test_x509_like_algorithm_identifier() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
AlgorithmIdentifier ::= SEQUENCE {
algorithm OBJECT IDENTIFIER,
parameters OCTET STRING OPTIONAL
}
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(code.contains("pub struct AlgorithmIdentifier {"));
assert!(code.contains("pub algorithm: ObjectIdentifier"));
assert!(code.contains("pub parameters: Option<OctetString>"));
}
#[test]
fn test_deeply_nested_constraints() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Port ::= INTEGER (0..65535)
ValidPort ::= Port (1024..65535)
HighPort ::= ValidPort (49152..65535)
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(code.contains("pub struct Port(u16);"));
assert!(code.contains("pub struct ValidPort(Port);"));
assert!(code.contains("pub struct HighPort(ValidPort);"));
}
#[test]
fn test_combined_size_from_pattern() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Pin ::= IA5String (SIZE (4..6) FROM ("0".."9"))
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(code.contains("pub struct Pin(IA5String);"));
assert!(code.contains("len() >= 4"));
assert!(code.contains("len() <= 6"));
assert!(code.contains("'0'"));
assert!(code.contains("'9'"));
}
#[test]
fn test_multiple_definitions_interaction() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Age ::= INTEGER (0..150)
Name ::= IA5String (SIZE (1..64))
Person ::= SEQUENCE {
name Name,
age Age
}
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(code.contains("pub struct Age(u8);"));
assert!(code.contains("pub struct Name(IA5String);"));
assert!(code.contains("pub struct Person {"));
assert!(code.contains("pub name: Name"));
assert!(code.contains("pub age: Age"));
}
#[test]
fn test_empty_constraint() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
EmptySeq ::= SEQUENCE {}
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(code.contains("pub struct EmptySeq {"));
assert!(code.contains("}"));
}
#[test]
fn test_large_union() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
DayOfMonth ::= INTEGER (1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10)
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(code.contains("pub struct DayOfMonth(i64);"));
assert!(code.contains("value == 1"));
assert!(code.contains("value == 10"));
}
#[test]
fn test_set_vs_sequence_ordering() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
MySeq ::= SEQUENCE {
first INTEGER,
second BOOLEAN
}
MySet ::= SET {
first INTEGER,
second BOOLEAN
}
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(code.contains("pub struct MySeq {"));
assert!(code.contains("pub struct MySet {"));
let seq_idx = code.find("MySeq").unwrap();
let seq_first = code[seq_idx..].find("first").unwrap();
let seq_second = code[seq_idx..].find("second").unwrap();
assert!(seq_first < seq_second);
}
#[test]
fn test_real_world_ipa_keytab_schema() {
let input = r#"
KeytabModule DEFINITIONS ::= BEGIN
Int32 ::= INTEGER (-2147483648..2147483647)
GetKeytabControl ::= CHOICE {
newkeys [0] GKNewKeys,
curkeys [1] GKCurrentKeys,
reply [2] GKReply
}
GKNewKeys ::= SEQUENCE {
serviceIdentity [0] OCTET STRING,
enctypes [1] SEQUENCE OF Int32,
password [2] OCTET STRING OPTIONAL
}
GKCurrentKeys ::= SEQUENCE {
serviceIdentity [0] OCTET STRING
}
GKReply ::= SEQUENCE {
newkvno Int32,
keys SEQUENCE OF KrbKey
}
KrbKey ::= SEQUENCE {
key [0] TypeValuePair,
salt [1] TypeValuePair OPTIONAL,
s2kparams [2] OCTET STRING OPTIONAL
}
TypeValuePair ::= SEQUENCE {
type [0] Int32,
value [1] OCTET STRING
}
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(code.contains("pub struct Int32(i32)"));
assert!(code.contains("(-2147483648..=2147483647).contains(&value)"));
assert!(code.contains("pub enum GetKeytabControl"));
assert!(code.contains("Newkeys(GKNewKeys)"));
assert!(code.contains("Curkeys(GKCurrentKeys)"));
assert!(code.contains("Reply(GKReply)"));
assert!(code.contains("pub struct GKNewKeys"));
assert!(code.contains("asn1(tag(0, explicit))"));
assert!(code.contains("pub service_identity: OctetString"));
assert!(code.contains("pub enctypes: Vec<Int32>"));
assert!(code.contains("pub keys: Vec<KrbKey>"));
assert!(code.contains("pub password: Option<OctetString>"));
assert!(code.contains("pub salt: Option<TypeValuePair>"));
assert!(code.contains("pub struct TypeValuePair"));
}
#[test]
fn test_real_world_ldap_rfc4511_schema() {
let input = r#"
LDAPModule DEFINITIONS ::= BEGIN
LDAPMessage ::= SEQUENCE {
messageID MessageID,
protocolOp CHOICE {
bindRequest [0] BindRequest,
searchRequest [1] SearchRequest,
modifyRequest [2] ModifyRequest
},
controls [0] Controls OPTIONAL
}
MessageID ::= INTEGER (0..2147483647)
BindRequest ::= SEQUENCE {
version INTEGER (1..127),
name OCTET STRING,
authentication CHOICE {
simple [0] OCTET STRING,
sasl [1] SaslCredentials
}
}
SaslCredentials ::= SEQUENCE {
mechanism OCTET STRING,
credentials OCTET STRING OPTIONAL
}
SearchRequest ::= SEQUENCE {
baseObject OCTET STRING,
scope INTEGER (0..2),
derefAliases INTEGER (0..3),
sizeLimit INTEGER (0..2147483647),
timeLimit INTEGER (0..2147483647),
typesOnly BOOLEAN
}
ModifyRequest ::= SEQUENCE {
object OCTET STRING,
changes SEQUENCE OF Change
}
Change ::= SEQUENCE {
operation INTEGER (0..2),
modification OCTET STRING
}
Controls ::= SEQUENCE OF Control
Control ::= SEQUENCE {
controlType OCTET STRING,
criticality BOOLEAN DEFAULT FALSE,
controlValue OCTET STRING OPTIONAL
}
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(code.contains("pub struct MessageID(u32)"));
assert!(code.contains("(0..=2147483647).contains(&value)"));
assert!(code.contains("pub struct LDAPMessage"));
assert!(code.contains("pub message_id: MessageID"));
assert!(code.contains("pub protocol_op:"));
assert!(code.contains("pub struct BindRequest"));
assert!(code.contains("pub version: Integer"));
assert!(code.contains("pub name: OctetString"));
assert!(code.contains("pub authentication:"));
assert!(code.contains("pub struct SaslCredentials"));
assert!(code.contains("pub mechanism: OctetString"));
assert!(code.contains("pub struct SearchRequest"));
assert!(code.contains("pub base_object: OctetString"));
assert!(code.contains("pub scope: Integer"));
assert!(code.contains("pub deref_aliases: Integer"));
assert!(code.contains("pub types_only: Boolean"));
assert!(code.contains("pub type Controls = Vec<Control>"));
assert!(code.contains("pub struct ModifyRequest"));
assert!(code.contains("pub changes: Vec<Change>"));
assert!(code.contains("pub struct Change"));
assert!(code.contains("pub operation: Integer"));
assert!(code.contains("pub struct Control"));
assert!(code.contains("pub control_type: OctetString"));
assert!(code.contains("pub criticality: Option<Boolean>"));
assert!(!code.contains("impl Default for Control"));
assert!(code.contains("pub credentials: Option<OctetString>"));
assert!(code.contains("pub control_value: Option<OctetString>"));
assert!(code.contains("pub controls: Option<Vec<Control>>"));
}
#[test]
fn test_real_world_kerberos_rfc4120_schema() {
let input = r#"
KerberosModule DEFINITIONS ::= BEGIN
Int32 ::= INTEGER (-2147483648..2147483647)
UInt32 ::= INTEGER (0..4294967295)
KerberosTime ::= GeneralizedTime
Realm ::= IA5String
PrincipalName ::= SEQUENCE {
name-type [0] Int32,
name-string [1] SEQUENCE OF IA5String
}
Ticket ::= SEQUENCE {
tkt-vno [0] INTEGER (5),
realm [1] Realm,
sname [2] PrincipalName,
enc-part [3] EncryptedData
}
EncryptedData ::= SEQUENCE {
etype [0] Int32,
kvno [1] UInt32 OPTIONAL,
cipher [2] OCTET STRING
}
KDC-REQ-BODY ::= SEQUENCE {
kdc-options [0] OCTET STRING,
cname [1] PrincipalName OPTIONAL,
realm [2] Realm,
sname [3] PrincipalName OPTIONAL,
from [4] KerberosTime OPTIONAL,
till [5] KerberosTime,
rtime [6] KerberosTime OPTIONAL,
nonce [7] UInt32,
etype [8] SEQUENCE OF Int32
}
AS-REQ ::= SEQUENCE {
pvno [1] INTEGER (5),
msg-type [2] INTEGER (10),
req-body [4] KDC-REQ-BODY
}
KDC-REP ::= SEQUENCE {
pvno [0] INTEGER (5),
msg-type [1] INTEGER,
crealm [3] Realm,
cname [4] PrincipalName,
ticket [5] Ticket,
enc-part [6] EncryptedData
}
Checksum ::= SEQUENCE {
cksumtype [0] Int32,
checksum [1] OCTET STRING
}
HostAddress ::= SEQUENCE {
addr-type [0] Int32,
address [1] OCTET STRING
}
HostAddresses ::= SEQUENCE OF HostAddress
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(code.contains("pub struct Int32(i32)"));
assert!(code.contains("(-2147483648..=2147483647).contains(&value)"));
assert!(code.contains("pub struct UInt32(u32)"));
assert!(code.contains("(0..=4294967295).contains(&value)"));
assert!(code.contains("pub type KerberosTime = GeneralizedTime"));
assert!(code.contains("pub type Realm = IA5String"));
assert!(code.contains("pub struct PrincipalName"));
assert!(code.contains("asn1(tag(0, explicit))"));
assert!(code.contains("pub name_type: Int32"));
assert!(code.contains("asn1(tag(1, explicit))"));
assert!(code.contains("pub name_string: Vec<IA5String>"));
assert!(code.contains("pub struct Ticket"));
assert!(code.contains("pub tkt_vno: Integer"));
assert!(code.contains("pub realm: Realm"));
assert!(code.contains("pub sname: PrincipalName"));
assert!(code.contains("pub enc_part: EncryptedData"));
assert!(code.contains("pub struct EncryptedData"));
assert!(code.contains("pub etype: Int32"));
assert!(code.contains("pub kvno: Option<UInt32>"));
assert!(code.contains("pub cipher: OctetString"));
assert!(code.contains("pub struct KdcReqBody"));
assert!(code.contains("pub kdc_options: OctetString"));
assert!(code.contains("pub cname: Option<PrincipalName>"));
assert!(code.contains("pub sname: Option<PrincipalName>"));
assert!(code.contains("pub from: Option<KerberosTime>"));
assert!(code.contains("pub till: KerberosTime"));
assert!(code.contains("pub rtime: Option<KerberosTime>"));
assert!(code.contains("pub nonce: UInt32"));
assert!(code.contains("pub etype: Vec<Int32>"));
assert!(code.contains("pub struct AsReq"));
assert!(code.contains("pub pvno: Integer"));
assert!(code.contains("pub msg_type: Integer"));
assert!(code.contains("pub req_body: KdcReqBody"));
assert!(code.contains("pub struct KdcRep"));
assert!(code.contains("pub crealm: Realm"));
assert!(code.contains("pub ticket: Ticket"));
assert!(code.contains("pub struct Checksum"));
assert!(code.contains("pub cksumtype: Int32"));
assert!(code.contains("pub struct HostAddress"));
assert!(code.contains("pub addr_type: Int32"));
assert!(code.contains("pub address: OctetString"));
assert!(code.contains("pub type HostAddresses = Vec<HostAddress>"));
}
#[test]
fn test_real_world_gssapi_spnego_rfc4178_schema() {
let input = r#"
SPNEGOModule DEFINITIONS ::= BEGIN
MechType ::= OBJECT IDENTIFIER
MechTypeList ::= SEQUENCE OF MechType
ContextFlags ::= BIT STRING
NegState ::= INTEGER (0..3)
NegTokenInit ::= SEQUENCE {
mechTypes [0] MechTypeList OPTIONAL,
reqFlags [1] ContextFlags OPTIONAL,
mechToken [2] OCTET STRING OPTIONAL,
mechListMIC [3] OCTET STRING OPTIONAL
}
NegTokenResp ::= SEQUENCE {
negState [0] NegState OPTIONAL,
supportedMech [1] MechType OPTIONAL,
responseToken [2] OCTET STRING OPTIONAL,
mechListMIC [3] OCTET STRING OPTIONAL
}
NegotiationToken ::= CHOICE {
negTokenInit [0] NegTokenInit,
negTokenResp [1] NegTokenResp
}
InitialContextToken ::= SEQUENCE {
thisMech MechType,
innerContextToken OCTET STRING
}
MICToken ::= SEQUENCE {
tokId OCTET STRING,
sndSeq INTEGER (0..4294967295),
sgnCksum OCTET STRING
}
WrapToken ::= SEQUENCE {
tokId OCTET STRING,
flags BIT STRING,
filler OCTET STRING,
sndSeq INTEGER (0..4294967295),
confounder OCTET STRING OPTIONAL,
data OCTET STRING
}
GSSHeader ::= SEQUENCE {
oid MechType,
tokenLength INTEGER (0..65535)
}
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(code.contains("pub type MechType = ObjectIdentifier"));
assert!(code.contains("pub type MechTypeList = Vec<MechType>"));
assert!(code.contains("pub type ContextFlags = BitString"));
assert!(code.contains("pub struct NegState(u8)"));
assert!(code.contains("(0..=3).contains(&value)"));
assert!(code.contains("pub struct NegTokenInit"));
assert!(code.contains("asn1(tag(0, explicit))"));
assert!(code.contains("pub mech_types: Option<Vec<MechType>>"));
assert!(code.contains("asn1(tag(1, explicit))"));
assert!(code.contains("pub req_flags: Option<ContextFlags>"));
assert!(code.contains("asn1(tag(2, explicit))"));
assert!(code.contains("pub mech_token: Option<OctetString>"));
assert!(code.contains("asn1(tag(3, explicit))"));
assert!(code.contains("pub mech_list_mic: Option<OctetString>"));
assert!(code.contains("pub struct NegTokenResp"));
assert!(code.contains("pub neg_state: Option<NegState>"));
assert!(code.contains("pub supported_mech: Option<MechType>"));
assert!(code.contains("pub response_token: Option<OctetString>"));
assert!(code.contains("pub enum NegotiationToken"));
assert!(code.contains("NegTokenInit(NegTokenInit)"));
assert!(code.contains("NegTokenResp(NegTokenResp)"));
assert!(code.contains("pub struct InitialContextToken"));
assert!(code.contains("pub this_mech: MechType"));
assert!(code.contains("pub inner_context_token: OctetString"));
assert!(code.contains("pub struct MICToken"));
assert!(code.contains("pub tok_id: OctetString"));
assert!(code.contains("pub snd_seq: Integer"));
assert!(code.contains("pub sgn_cksum: OctetString"));
assert!(code.contains("pub struct WrapToken"));
assert!(code.contains("pub flags: BitString"));
assert!(code.contains("pub filler: OctetString"));
assert!(code.contains("pub confounder: Option<OctetString>"));
assert!(code.contains("pub data: OctetString"));
assert!(code.contains("pub struct GSSHeader"));
assert!(code.contains("pub oid: MechType"));
assert!(code.contains("pub token_length: Integer"));
}
#[test]
fn test_detect_no_cycles_single_module() {
let m = parse("Foo DEFINITIONS ::= BEGIN END").unwrap();
let cycles = detect_cycles(&[m]);
assert!(cycles.is_empty());
}
#[test]
fn test_detect_no_cycles_linear_chain() {
let a = parse("ModA DEFINITIONS ::= BEGIN IMPORTS X FROM ModB; END").unwrap();
let b = parse("ModB DEFINITIONS ::= BEGIN END").unwrap();
let cycles = detect_cycles(&[a, b]);
assert!(cycles.is_empty());
}
#[test]
fn test_detect_direct_cycle() {
let a = parse("ModA DEFINITIONS ::= BEGIN IMPORTS X FROM ModB; END").unwrap();
let b = parse("ModB DEFINITIONS ::= BEGIN IMPORTS Y FROM ModA; END").unwrap();
let cycles = detect_cycles(&[a, b]);
assert!(!cycles.is_empty(), "expected a cycle to be detected");
}
#[test]
fn test_topological_order_single_module() {
let m = parse("Foo DEFINITIONS ::= BEGIN END").unwrap();
let order = topological_order(&[m]);
assert_eq!(order, vec![0]);
}
#[test]
fn test_topological_order_dependency_first() {
let a = parse("ModA DEFINITIONS ::= BEGIN IMPORTS X FROM ModB; END").unwrap();
let b = parse("ModB DEFINITIONS ::= BEGIN END").unwrap();
let order = topological_order(&[a, b]);
assert_eq!(order[0], 1, "ModB (dependency) must be generated first");
assert_eq!(order[1], 0, "ModA (dependent) must be generated second");
}
#[test]
fn test_c_helpers_sequence_init_validate_print() {
let schema = "
MyModule DEFINITIONS ::= BEGIN
Rec ::= SEQUENCE {
id INTEGER,
label OCTET STRING OPTIONAL
}
END
";
let m = parse(schema).unwrap();
let cfg = CCodeGenConfig {
generate_helpers: true,
..Default::default()
};
let code = generate_c_with_config(&m, cfg).unwrap();
assert!(code.contains("#include <stdio.h>"));
assert!(code.contains("static inline void rec_init(Rec* value)"));
assert!(code.contains("memset(value, 0, sizeof(Rec))"));
assert!(code.contains("static inline SyntaErrorCode rec_validate(const Rec* value)"));
assert!(code.contains("if (value->id == NULL) return SyntaErrorCode_InvalidArgument"));
assert!(code.contains(
"if (value->has_label && value->label == NULL) return SyntaErrorCode_InvalidArgument"
));
assert!(code.contains("static inline void rec_print(const Rec* value, FILE* stream)"));
assert!(code.contains("fprintf(stream, \"Rec(null)"));
}
#[test]
fn test_c_helpers_choice_validate_print() {
let schema = "
MyModule DEFINITIONS ::= BEGIN
Tag ::= CHOICE {
optA INTEGER,
optB OCTET STRING
}
END
";
let m = parse(schema).unwrap();
let cfg = CCodeGenConfig {
generate_helpers: true,
..Default::default()
};
let code = generate_c_with_config(&m, cfg).unwrap();
assert!(code.contains("static inline SyntaErrorCode tag_validate(const Tag* value)"));
assert!(code.contains("switch (value->tag)"));
assert!(code.contains("TagTag_OptA"));
assert!(code.contains("TagTag_OptB"));
assert!(code.contains("return SyntaErrorCode_InvalidEncoding"));
assert!(code.contains("static inline void tag_print(const Tag* value, FILE* stream)"));
}
#[test]
fn test_c_no_helpers_by_default() {
let schema = "MyModule DEFINITIONS ::= BEGIN Foo ::= SEQUENCE { x INTEGER } END";
let m = parse(schema).unwrap();
let code = generate_c_with_config(&m, CCodeGenConfig::default()).unwrap();
assert!(
!code.contains("foo_init"),
"helpers should not be generated by default"
);
assert!(
!code.contains("foo_validate"),
"helpers should not be generated by default"
);
assert!(
!code.contains("foo_print"),
"helpers should not be generated by default"
);
}
#[test]
fn test_cmake_single_module_defaults() {
let m = parse("MyModule DEFINITIONS ::= BEGIN Foo ::= INTEGER END").unwrap();
let out = generate_cmake(&[m], &[0], CMakeConfig::default()).unwrap();
assert!(out.contains("cmake_minimum_required(VERSION 3.10)"));
assert!(out.contains("project(MyModule C)"));
assert!(out.contains("set(CMAKE_C_STANDARD 99)"));
assert!(out.contains("add_library(MyModule STATIC"));
assert!(out.contains("my_module.c"));
assert!(out.contains("Synta::Synta"));
assert!(out.contains("SYNTA_ROOT"));
assert!(out.contains("CACHE PATH"));
}
#[test]
fn test_cmake_synta_root_hardcoded() {
let m = parse("Cert DEFINITIONS ::= BEGIN END").unwrap();
let cfg = CMakeConfig {
synta_root: Some("/opt/synta".to_string()),
shared_library: false,
};
let out = generate_cmake(&[m], &[0], cfg).unwrap();
assert!(out.contains("set(_synta_root \"/opt/synta\")"));
assert!(!out.contains("CACHE PATH"));
}
#[test]
fn test_cmake_shared_library() {
let m = parse("Foo DEFINITIONS ::= BEGIN END").unwrap();
let cfg = CMakeConfig {
shared_library: true,
..Default::default()
};
let out = generate_cmake(&[m], &[0], cfg).unwrap();
assert!(out.contains("add_library(Foo SHARED"));
assert!(!out.contains("add_library(Foo STATIC"));
}
#[test]
fn test_cmake_multi_module_dep_order() {
let a = parse("ModA DEFINITIONS ::= BEGIN IMPORTS X FROM ModB; END").unwrap();
let b = parse("ModB DEFINITIONS ::= BEGIN END").unwrap();
let order = vec![1usize, 0usize];
let out = generate_cmake(&[a, b], &order, CMakeConfig::default()).unwrap();
assert!(out.contains("add_library(ModB STATIC"));
assert!(out.contains("add_library(ModA STATIC"));
assert!(out.contains("target_link_libraries(ModA PUBLIC\n ModB"));
}
#[test]
fn test_meson_single_module_defaults() {
let m = parse("MyModule DEFINITIONS ::= BEGIN Foo ::= INTEGER END").unwrap();
let out = generate_meson(&[m], &[0], MesonConfig::default()).unwrap();
assert!(out.contains("project('my_module', 'c'"));
assert!(out.contains("c_std=c99"));
assert!(out.contains("library('my_module',"));
assert!(out.contains("sources: ['my_module.c']"));
assert!(out.contains("synta_dep = dependency('synta')"));
assert!(out.contains("my_module_dep = declare_dependency("));
}
#[test]
fn test_meson_synta_root_hardcoded() {
let m = parse("Cert DEFINITIONS ::= BEGIN END").unwrap();
let cfg = MesonConfig {
synta_root: Some("/opt/synta".to_string()),
shared_library: false,
};
let out = generate_meson(&[m], &[0], cfg).unwrap();
assert!(out.contains("cc.find_library('synta'"));
assert!(out.contains("'/opt/synta' / 'target' / 'release'"));
assert!(out.contains("'/opt/synta' / 'include'"));
assert!(!out.contains("dependency('synta')"));
}
#[test]
fn test_meson_shared_library() {
let m = parse("Foo DEFINITIONS ::= BEGIN END").unwrap();
let cfg = MesonConfig {
shared_library: true,
..Default::default()
};
let out = generate_meson(&[m], &[0], cfg).unwrap();
assert!(out.contains("shared_library('foo',"));
assert!(!out.contains("\nfoo_lib = library("));
}
#[test]
fn test_meson_multi_module_dep_order() {
let a = parse("ModA DEFINITIONS ::= BEGIN IMPORTS X FROM ModB; END").unwrap();
let b = parse("ModB DEFINITIONS ::= BEGIN END").unwrap();
let order = vec![1usize, 0usize];
let out = generate_meson(&[a, b], &order, MesonConfig::default()).unwrap();
assert!(out.contains("library('mod_b',"));
assert!(out.contains("library('mod_a',"));
assert!(out.contains("dependencies: [synta_dep, mod_b_dep]"));
}
fn make_simple_seq_module() -> Module {
Module {
name: "TestModule".to_string(),
oid: None,
tagging_mode: None,
imports: vec![],
exports: vec![],
definitions: vec![Definition {
name: "SimpleSeq".to_string(),
ty: Type::Sequence(vec![
SequenceField {
name: "version".to_string(),
ty: Type::Integer(None, vec![]),
optional: false,
default: None,
},
SequenceField {
name: "name".to_string(),
ty: Type::OctetString(None),
optional: false,
default: None,
},
]),
}],
values: vec![],
}
}
#[test]
fn test_arena_type_emitted_with_flag() {
let module = make_simple_seq_module();
let config = CCodeGenConfig {
arena_mode: true,
..Default::default()
};
let result = generate_c_with_config(&module, config).unwrap();
assert!(
result.contains("SyntaArena"),
"SyntaArena type must be present"
);
assert!(
result.contains("SYNTA_ARENA_MAX_HANDLES"),
"capacity macro must be present"
);
assert!(
result.contains("synta_arena_init"),
"synta_arena_init must be present"
);
assert!(
result.contains("_synta_arena_track"),
"_synta_arena_track must be present"
);
assert!(
result.contains("synta_arena_free_all"),
"synta_arena_free_all must be present"
);
}
#[test]
fn test_arena_decode_prototype_generated() {
let module = make_simple_seq_module();
let config = CCodeGenConfig {
arena_mode: true,
..Default::default()
};
let result = generate_c_with_config(&module, config).unwrap();
assert!(
result.contains("simple_seq_decode_arena"),
"arena decode prototype must be emitted"
);
assert!(
result.contains("SyntaArena* arena"),
"prototype must include arena parameter"
);
assert!(result.contains("simple_seq_decode(SyntaDecoder*"));
}
#[test]
fn test_arena_not_emitted_by_default() {
let module = make_simple_seq_module();
let result = generate_c_with_config(&module, CCodeGenConfig::default()).unwrap();
assert!(
!result.contains("SyntaArena"),
"SyntaArena must NOT appear without --arena"
);
assert!(
!result.contains("_decode_arena"),
"_decode_arena must NOT appear without --arena"
);
}
#[test]
fn test_arena_sequence_tracks_pointers() {
let module = make_simple_seq_module();
let config = CImplConfig {
header_file: "test.h".to_string(),
arena_mode: true,
pattern_mode: PatternMode::Skip,
with_containing: false,
};
let result = generate_c_impl(&module, config).unwrap();
assert!(
result.contains("simple_seq_decode_arena"),
"arena decode function must be generated"
);
assert!(
result.contains("synta_integer_free"),
"integer pointer must be tracked"
);
assert!(
result.contains("synta_octet_string_free"),
"octet-string pointer must be tracked"
);
assert!(
result.contains("_synta_arena_track"),
"_synta_arena_track must appear in implementation"
);
}
#[test]
fn test_arena_sequence_of_tracks_array() {
let module = Module {
values: vec![],
name: "TestModule".to_string(),
oid: None,
tagging_mode: None,
imports: vec![],
exports: vec![],
definitions: vec![Definition {
name: "IntList".to_string(),
ty: Type::SequenceOf(Box::new(Type::Integer(None, vec![])), None),
}],
};
let config = CImplConfig {
header_file: "test.h".to_string(),
arena_mode: true,
pattern_mode: PatternMode::Skip,
with_containing: false,
};
let result = generate_c_impl(&module, config).unwrap();
assert!(
result.contains("int_list_decode_arena"),
"arena decode for SEQUENCE OF must be generated"
);
assert!(
result.contains("_synta_arena_track(arena, out->items, free)"),
"array pointer must be tracked with free"
);
}
#[test]
fn test_arena_choice_tracks_active_branch() {
let module = Module {
values: vec![],
name: "TestModule".to_string(),
oid: None,
tagging_mode: None,
imports: vec![],
exports: vec![],
definitions: vec![Definition {
name: "MyChoice".to_string(),
ty: Type::Choice(vec![
ChoiceVariant {
name: "intVal".to_string(),
ty: Type::Integer(None, vec![]),
},
ChoiceVariant {
name: "strVal".to_string(),
ty: Type::OctetString(None),
},
]),
}],
};
let config = CImplConfig {
header_file: "test.h".to_string(),
arena_mode: true,
pattern_mode: PatternMode::Skip,
with_containing: false,
};
let result = generate_c_impl(&module, config).unwrap();
assert!(
result.contains("my_choice_decode_arena"),
"arena decode for CHOICE must be generated"
);
assert!(
result.contains("synta_integer_free"),
"integer variant must be tracked"
);
assert!(
result.contains("synta_octet_string_free"),
"octet-string variant must be tracked"
);
assert!(
result.contains("_synta_arena_track"),
"_synta_arena_track must appear in choice implementation"
);
}
#[test]
fn test_enumerated_rust_type_alias() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Status ::= ENUMERATED { active(0), inactive(1), pending(2) }
END
"#;
let module = parse(input).unwrap();
let code = gen(&module);
assert!(
code.contains("pub enum Status {"),
"ENUMERATED should produce a Rust enum; got:\n{}",
code
);
assert!(
code.contains("Active = 0,"),
"ENUMERATED enum should have variant Active = 0; got:\n{}",
code
);
assert!(
code.contains("TryFrom<Integer> for Status"),
"ENUMERATED should have TryFrom<Integer> impl; got:\n{}",
code
);
assert!(
code.contains("From<Status> for Integer"),
"ENUMERATED should have From<Status> for Integer impl; got:\n{}",
code
);
}
#[test]
fn test_enumerated_in_sequence_rust() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Direction ::= ENUMERATED { north(0), south(1), east(2), west(3) }
Route ::= SEQUENCE {
heading Direction,
optDir Direction OPTIONAL
}
END
"#;
let module = parse(input).unwrap();
let code = gen(&module);
assert!(
code.contains("pub heading: Direction"),
"ENUMERATED field should use the type-ref name; got:\n{}",
code
);
assert!(
code.contains("pub opt_dir: Option<Direction>"),
"OPTIONAL ENUMERATED field should be Option<Direction>; got:\n{}",
code
);
}
#[test]
fn test_enumerated_c_typedef() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Color ::= ENUMERATED { red(0), green(1), blue(2) }
END
"#;
let module = parse(input).unwrap();
let config = CCodeGenConfig::default();
let code = generate_c_with_config(&module, config).unwrap();
assert!(
code.contains("typedef enum Color Color;"),
"C codegen should forward-declare ENUMERATED as typedef enum; got:\n{}",
code
);
}
#[test]
fn test_enumerated_c_field_type() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Status ::= ENUMERATED { on(0), off(1) }
Device ::= SEQUENCE {
state Status
}
END
"#;
let module = parse(input).unwrap();
let config = CCodeGenConfig::default();
let code = generate_c_with_config(&module, config).unwrap();
assert!(
code.contains("Status state;"),
"ENUMERATED struct field should be the C enum type; got:\n{}",
code
);
assert!(
code.contains("typedef enum Status Status;"),
"ENUMERATED should have a typedef enum forward declaration; got:\n{}",
code
);
}
#[test]
fn test_enumerated_c_impl_decode_encode() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Level ::= ENUMERATED { low(0), medium(1), high(2) }
END
"#;
let module = parse(input).unwrap();
let config = CImplConfig {
header_file: "test.h".to_string(),
arena_mode: false,
pattern_mode: PatternMode::Skip,
with_containing: false,
};
let code = generate_c_impl(&module, config).unwrap();
assert!(
code.contains("synta_decode_integer"),
"ENUMERATED decode should use synta_decode_integer; got:\n{}",
code
);
assert!(
code.contains("synta_encode_integer"),
"ENUMERATED encode should use synta_encode_integer; got:\n{}",
code
);
}
#[test]
fn test_enumerated_c_impl_uses_integer_cast() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Priority ::= ENUMERATED { low(0), high(1) }
END
"#;
let module = parse(input).unwrap();
let config = CImplConfig {
header_file: "test.h".to_string(),
arena_mode: false,
pattern_mode: PatternMode::Skip,
with_containing: false,
};
let code = generate_c_impl(&module, config).unwrap();
assert!(
code.contains("synta_decode_integer"),
"ENUMERATED decode should use synta_decode_integer; got:\n{}",
code
);
assert!(
code.contains("synta_integer_to_i64"),
"ENUMERATED decode should extract i64 value; got:\n{}",
code
);
assert!(
code.contains("(enum Priority)"),
"ENUMERATED decode should cast to the enum type; got:\n{}",
code
);
assert!(
code.contains("synta_integer_new_i64"),
"ENUMERATED encode should wrap value in SyntaInteger; got:\n{}",
code
);
}
#[test]
fn test_enumerated_arena_no_heap_allocation() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Mode ::= ENUMERATED { fast(0), slow(1) }
Cfg ::= SEQUENCE {
mode Mode
}
END
"#;
let module = parse(input).unwrap();
let config = CImplConfig {
header_file: "test.h".to_string(),
arena_mode: true,
pattern_mode: PatternMode::Skip,
with_containing: false,
};
let code = generate_c_impl(&module, config).unwrap();
assert!(
code.contains("synta_integer_free"),
"ENUMERATED decode should free the temporary SyntaInteger; got:\n{}",
code
);
assert!(
code.contains("synta_integer_to_i64"),
"ENUMERATED decode should extract i64 from temporary SyntaInteger; got:\n{}",
code
);
}
#[test]
fn test_real_rust_type() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Measurement ::= REAL
END
"#;
let module = parse(input).unwrap();
let code = gen(&module);
assert!(
code.contains("f64"),
"REAL should map to f64 in Rust; got:\n{}",
code
);
}
#[test]
fn test_real_in_sequence_rust() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
DataPoint ::= SEQUENCE {
value REAL,
label UTF8String
}
END
"#;
let module = parse(input).unwrap();
let code = gen(&module);
assert!(
code.contains("pub value: f64"),
"REAL field should be f64; got:\n{}",
code
);
}
#[test]
fn test_real_c_field_type() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Sample ::= SEQUENCE {
temperature REAL
}
END
"#;
let module = parse(input).unwrap();
let config = CCodeGenConfig::default();
let code = generate_c_with_config(&module, config).unwrap();
assert!(
code.contains("double"),
"REAL struct field should be double in C; got:\n{}",
code
);
}
#[test]
fn test_real_c_impl_decode_encode() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Ratio ::= REAL
Container ::= SEQUENCE {
ratio Ratio
}
END
"#;
let module = parse(input).unwrap();
let config = CImplConfig {
header_file: "test.h".to_string(),
arena_mode: false,
pattern_mode: PatternMode::Skip,
with_containing: false,
};
let code = generate_c_impl(&module, config).unwrap();
assert!(
code.contains("synta_decode_real"),
"REAL decode should use synta_decode_real; got:\n{}",
code
);
assert!(
code.contains("synta_encode_real"),
"REAL encode should use synta_encode_real; got:\n{}",
code
);
}
#[test]
fn test_real_c_impl_delegates_to_library() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Weight ::= REAL
Item ::= SEQUENCE {
weight Weight
}
END
"#;
let module = parse(input).unwrap();
let config = CImplConfig {
header_file: "test.h".to_string(),
arena_mode: false,
pattern_mode: PatternMode::Skip,
with_containing: false,
};
let code = generate_c_impl(&module, config).unwrap();
assert!(
code.contains("return synta_decode_real(decoder, out);"),
"REAL type-alias decode should delegate directly to synta_decode_real; got:\n{}",
code
);
assert!(
code.contains("return synta_encode_real(encoder, *value);"),
"REAL type-alias encode should delegate directly to synta_encode_real; got:\n{}",
code
);
}
#[test]
fn test_real_arena_no_tracking() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Signal ::= SEQUENCE {
amplitude REAL
}
END
"#;
let module = parse(input).unwrap();
let config = CImplConfig {
header_file: "test.h".to_string(),
arena_mode: true,
pattern_mode: PatternMode::Skip,
with_containing: false,
};
let code = generate_c_impl(&module, config).unwrap();
assert!(
!code.contains("_synta_arena_track"),
"REAL in arena mode must NOT call _synta_arena_track; got:\n{}",
code
);
assert!(
code.contains("synta_decode_real"),
"REAL field decode in arena mode should still call synta_decode_real; got:\n{}",
code
);
}
#[test]
fn test_use_core_tryfrom() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Count ::= INTEGER (0..100)
END
"#;
let module = parse(input).unwrap();
let default_code = generate(&module).unwrap();
assert!(
default_code.contains("impl core::convert::TryFrom"),
"TryFrom should always use core::; got:\n{}",
default_code
);
assert!(
!default_code.contains("impl std::convert::TryFrom"),
"TryFrom should never emit std::; got:\n{}",
default_code
);
let core_config = CodeGenConfig {
use_core: true,
..Default::default()
};
let core_code = generate_with_config(&module, core_config).unwrap();
assert!(
core_code.contains("impl core::convert::TryFrom"),
"TryFrom should use core:: with use_core=true; got:\n{}",
core_code
);
}
#[test]
fn test_no_redundant_true_in_open_range() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Big ::= INTEGER (1..MAX)
Name ::= IA5String (SIZE (1..MAX))
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(
!code.contains("&& true"),
"spurious `&& true` in generated code:\n{}",
code
);
assert!(
!code.contains("true &&"),
"spurious `true &&` in generated code:\n{}",
code
);
assert!(
code.contains("value >= 1")
|| code.contains("value.len() >= 1")
|| code.contains("!value.as_str().is_empty()")
);
}
#[test]
fn test_extension_marker_in_sequence() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Msg ::= SEQUENCE {
id INTEGER,
...,
name UTF8String OPTIONAL
}
END
"#;
let module = parse(input).unwrap();
let code = gen(&module);
assert!(
code.contains("pub id: Integer"),
"field 'id' should be present; got:\n{}",
code
);
assert!(
code.contains("pub name: Option<Utf8String>"),
"field 'name' should be present after '...'; got:\n{}",
code
);
}
#[test]
fn test_extension_marker_in_choice() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Token ::= CHOICE {
init [0] INTEGER,
...,
resp [1] UTF8String
}
END
"#;
let module = parse(input).unwrap();
let code = gen(&module);
assert!(
code.contains("Init("),
"variant 'init' should be present; got:\n{}",
code
);
assert!(
code.contains("Resp("),
"variant 'resp' should be present after '...'; got:\n{}",
code
);
}
#[test]
fn test_extension_marker_in_enumerated() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
State ::= ENUMERATED { running(0), stopped(1), ..., paused(2) }
END
"#;
let module = parse(input).unwrap();
let enumerated = &module.definitions[0];
if let synta_codegen::ast::Type::Enumerated(values) = &enumerated.ty {
assert_eq!(
values.len(),
3,
"exactly 3 named values expected; got {}",
values.len()
);
} else {
panic!("expected Enumerated type; got {:?}", enumerated.ty);
}
let code = gen(&module);
assert!(
code.contains("State"),
"definition 'State' should appear in generated code; got:\n{}",
code
);
}
#[test]
fn test_application_tag_in_sequence_field() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Wrapper ::= SEQUENCE {
data [APPLICATION 0] IMPLICIT INTEGER
}
END
"#;
let module = parse(input).unwrap();
let code = gen(&module);
assert!(
code.contains("APPLICATION"),
"APPLICATION tag field should emit class comment; got:\n{}",
code
);
}
#[test]
fn test_application_tag_top_level_sequence() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Ticket ::= [APPLICATION 1] IMPLICIT SEQUENCE {
realm UTF8String,
sname UTF8String
}
END
"#;
let module = parse(input).unwrap();
let code = gen(&module);
assert!(
code.contains("APPLICATION") && code.contains("1"),
"top-level APPLICATION-tagged type should note the class; got:\n{}",
code
);
assert!(
code.contains("pub struct Ticket"),
"inner SEQUENCE should be generated as a struct; got:\n{}",
code
);
assert!(
code.contains("pub realm: Utf8String"),
"struct field 'realm' should be present; got:\n{}",
code
);
}
#[test]
fn test_context_specific_tag_unchanged() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Msg ::= SEQUENCE {
id [0] EXPLICIT INTEGER
}
END
"#;
let module = parse(input).unwrap();
let code = gen(&module);
assert!(
code.contains("asn1(tag(0, explicit))"),
"context-specific tag should emit asn1(tag(N, mode)); got:\n{}",
code
);
}
#[test]
fn test_named_bit_string_type_alias() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Flags ::= BIT STRING { delegFlag(0), mutualFlag(1), replayFlag(2) }
END
"#;
let module = parse(input).unwrap();
let code = gen(&module);
assert!(
code.contains("pub type Flags = BitString"),
"named BIT STRING without size should produce a type alias; got:\n{}",
code
);
assert!(
code.contains("DELEG_FLAG: usize = 0"),
"should have DELEG_FLAG bit constant at position 0; got:\n{}",
code
);
assert!(
code.contains("MUTUAL_FLAG: usize = 1"),
"should have MUTUAL_FLAG bit constant at position 1; got:\n{}",
code
);
assert!(
code.contains("REPLAY_FLAG: usize = 2"),
"should have REPLAY_FLAG bit constant at position 2; got:\n{}",
code
);
}
#[test]
fn test_named_bit_string_with_size_constraint() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
ContextFlags ::= BIT STRING { delegFlag(0), mutualFlag(1) } (SIZE (32..MAX))
END
"#;
let module = parse(input).unwrap();
let code = gen(&module);
assert!(
code.contains("pub struct ContextFlags(BitString)"),
"named BIT STRING with SIZE should produce a constrained newtype; got:\n{}",
code
);
assert!(
code.contains("DELEG_FLAG: usize = 0"),
"should have DELEG_FLAG bit constant; got:\n{}",
code
);
assert!(
code.contains("MUTUAL_FLAG: usize = 1"),
"should have MUTUAL_FLAG bit constant; got:\n{}",
code
);
assert!(
code.contains("value.bit_len() >= 32"),
"BitString size validation must use bit_len(), not len(); got:\n{}",
code
);
}
#[test]
fn test_enumerated_generates_rust_enum() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Color ::= ENUMERATED { red(0), green(1), blue(2) }
END
"#;
let module = parse(input).unwrap();
let code = gen(&module);
assert!(
code.contains("#[repr(i64)]"),
"ENUMERATED should be #[repr(i64)]; got:\n{}",
code
);
assert!(
code.contains("pub enum Color {"),
"ENUMERATED should be a pub enum; got:\n{}",
code
);
assert!(
code.contains("Red = 0"),
"should have Red = 0 variant; got:\n{}",
code
);
assert!(
code.contains("Green = 1"),
"should have Green = 1 variant; got:\n{}",
code
);
assert!(
code.contains("Blue = 2"),
"should have Blue = 2 variant; got:\n{}",
code
);
}
#[test]
fn test_enumerated_try_from_integer() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
NegState ::= ENUMERATED { acceptCompleted(0), acceptIncomplete(1), reject(2), requestMic(3) }
END
"#;
let module = parse(input).unwrap();
let code = gen(&module);
assert!(
code.contains("TryFrom<Integer> for NegState"),
"ENUMERATED should have TryFrom<Integer>; got:\n{}",
code
);
assert!(
code.contains("0 => Ok(NegState::AcceptCompleted)"),
"should have match arm for 0; got:\n{}",
code
);
assert!(
code.contains("_ => Err(\"unknown ENUMERATED value\")"),
"should have catch-all error arm; got:\n{}",
code
);
assert!(
code.contains("From<NegState> for Integer"),
"ENUMERATED should have From<NegState> for Integer; got:\n{}",
code
);
}
#[test]
fn test_sequence_of_anonymous_sequence() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Pairs ::= SEQUENCE OF SEQUENCE {
key INTEGER,
value OCTET STRING
}
END
"#;
let module = parse(input).unwrap();
let code = gen(&module);
assert!(
code.contains("pub struct PairsElement"),
"anonymous inner SEQUENCE should become PairsElement struct; got:\n{}",
code
);
assert!(
code.contains("pub key: Integer"),
"PairsElement should have 'key' field; got:\n{}",
code
);
assert!(
code.contains("pub value: OctetString"),
"PairsElement should have 'value' field; got:\n{}",
code
);
assert!(
code.contains("pub type Pairs = Vec<PairsElement>"),
"collection type should be Vec<PairsElement>; got:\n{}",
code
);
assert!(
!code.contains("/* nested sequence */"),
"should not emit placeholder comment; got:\n{}",
code
);
}
#[test]
fn test_sequence_of_anonymous_choice() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Items ::= SEQUENCE OF CHOICE {
num INTEGER,
text OCTET STRING
}
END
"#;
let module = parse(input).unwrap();
let code = gen(&module);
assert!(
code.contains("pub enum ItemsElement"),
"anonymous inner CHOICE should become ItemsElement enum; got:\n{}",
code
);
assert!(
code.contains("pub type Items = Vec<ItemsElement>"),
"collection type should be Vec<ItemsElement>; got:\n{}",
code
);
assert!(
!code.contains("/* nested choice */"),
"should not emit placeholder comment; got:\n{}",
code
);
}
#[test]
fn test_sequence_of_anonymous_sequence_parsed() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Records ::= SEQUENCE OF SEQUENCE {
id INTEGER,
label OCTET STRING OPTIONAL
}
END
"#;
let module = parse(input).unwrap();
assert_eq!(module.definitions.len(), 1);
match &module.definitions[0].ty {
synta_codegen::ast::Type::SequenceOf(inner, _) => {
assert!(
matches!(inner.as_ref(), synta_codegen::ast::Type::Sequence(_)),
"inner type should be Sequence, got: {:?}",
inner
);
}
other => panic!("expected SequenceOf, got: {:?}", other),
}
}
#[test]
fn test_generalstring_as_type_alias() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
KerberosString ::= GeneralString
END
"#;
let module = parse(input).unwrap();
let code = gen(&module);
assert!(
code.contains("pub type KerberosString = GeneralString"),
"GeneralString should generate a type alias; got:\n{}",
code
);
assert!(
code.contains("use synta::types::string::*"),
"generated file must import synta string types; got:\n{}",
code
);
}
#[test]
fn test_generalstring_field_in_sequence() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Name ::= SEQUENCE {
value GeneralString
}
END
"#;
let module = parse(input).unwrap();
let code = gen(&module);
assert!(
code.contains("pub value: GeneralString"),
"GeneralString field should render as GeneralString; got:\n{}",
code
);
}
#[test]
fn test_real_world_gssapi_kerberos_rfc4121_schema() {
let input = r#"
GssapiKerberos DEFINITIONS IMPLICIT TAGS ::= BEGIN
InitialContextToken ::= [APPLICATION 0] IMPLICIT SEQUENCE {
thisMech OBJECT IDENTIFIER
}
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(
code.contains("pub struct InitialContextToken"),
"InitialContextToken struct missing; got:\n{}",
code
);
assert!(
code.contains("pub this_mech: ObjectIdentifier"),
"thisMech field should render as ObjectIdentifier; got:\n{}",
code
);
assert!(
code.contains("APPLICATION 0"),
"Expected APPLICATION 0 doc comment; got:\n{}",
code
);
}
#[test]
fn test_real_world_kerberos_fast_rfc6113_schema() {
let input = r#"
KerberosFast DEFINITIONS IMPLICIT TAGS ::= BEGIN
-- Stubs for KerberosV5 primitives
Int32 ::= INTEGER (-2147483648..2147483647)
UInt32 ::= INTEGER (0..4294967295)
KerberosTime ::= GeneralizedTime
Microseconds ::= INTEGER (0..999999)
Realm ::= GeneralString
PA-DATA ::= SEQUENCE {
padata-type [1] Int32,
padata-value [2] OCTET STRING
}
KDC-REQ-BODY ::= SEQUENCE {
kdc-options [0] BIT STRING,
realm [2] Realm,
nonce [7] UInt32
}
Checksum ::= SEQUENCE {
cksumtype [0] Int32,
checksum [1] OCTET STRING
}
EncryptedData ::= SEQUENCE {
etype [0] Int32,
kvno [1] UInt32 OPTIONAL,
cipher [2] OCTET STRING
}
EncryptionKey ::= SEQUENCE {
keytype [0] Int32,
keyvalue [1] OCTET STRING
}
PrincipalName ::= SEQUENCE {
name-type [0] Int32,
name-string [1] SEQUENCE OF GeneralString
}
-- FAST-specific types
FastOptions ::= BIT STRING {
reserved(0),
hide-client-names(1),
kdc-follow-referrals(16)
} (SIZE (32..MAX))
KrbFastArmor ::= SEQUENCE {
armor-type [0] Int32,
armor-value [1] OCTET STRING
}
KrbFastReq ::= SEQUENCE {
fast-options [0] FastOptions,
padata [1] SEQUENCE OF PA-DATA,
req-body [2] KDC-REQ-BODY
}
KrbFastArmoredReq ::= SEQUENCE {
armor [0] KrbFastArmor OPTIONAL,
req-checksum [1] Checksum,
enc-fast-req [2] EncryptedData
}
PA-FX-FAST-REQUEST ::= CHOICE {
armored-data [0] KrbFastArmoredReq,
...
}
KrbFastFinished ::= SEQUENCE {
timestamp [0] KerberosTime,
usec [1] Microseconds,
crealm [2] Realm,
cname [3] PrincipalName,
ticket-checksum [4] Checksum
}
KrbFastResponse ::= SEQUENCE {
padata [0] SEQUENCE OF PA-DATA,
strengthen-key [1] EncryptionKey OPTIONAL,
finished [2] KrbFastFinished OPTIONAL,
nonce [3] UInt32
}
KrbFastArmoredRep ::= SEQUENCE {
enc-fast-rep [0] EncryptedData
}
PA-FX-FAST-REPLY ::= CHOICE {
armored-data [0] KrbFastArmoredRep,
...
}
KrbFastErrorData ::= SEQUENCE {
padata [0] SEQUENCE OF PA-DATA
}
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(
code.contains("pub struct FastOptions(BitString)"),
"FastOptions newtype missing; got:\n{}",
code
);
assert!(
code.contains("HIDE_CLIENT_NAMES"),
"hide-client-names constant missing; got:\n{}",
code
);
assert!(
code.contains("KDC_FOLLOW_REFERRALS"),
"kdc-follow-referrals constant missing; got:\n{}",
code
);
assert!(
code.contains("pub struct KrbFastArmor"),
"KrbFastArmor struct missing; got:\n{}",
code
);
assert!(
code.contains("pub armor_type: Int32"),
"armor_type field should use Int32 alias; got:\n{}",
code
);
assert!(
code.contains("pub armor_value: OctetString"),
"armor_value field missing; got:\n{}",
code
);
assert!(
code.contains("pub struct KrbFastReq"),
"KrbFastReq struct missing; got:\n{}",
code
);
assert!(
code.contains("pub fast_options: FastOptions"),
"fast_options field missing; got:\n{}",
code
);
assert!(
code.contains("pub enum PaFxFastRequest"),
"PA-FX-FAST-REQUEST enum missing; got:\n{}",
code
);
assert!(
code.contains("ArmoredData(KrbFastArmoredReq)"),
"ArmoredData variant missing; got:\n{}",
code
);
assert!(
code.contains("pub struct KrbFastFinished"),
"KrbFastFinished struct missing; got:\n{}",
code
);
assert!(
code.contains("pub ticket_checksum: Checksum"),
"ticket_checksum field missing; got:\n{}",
code
);
assert!(
code.contains("pub struct KrbFastResponse"),
"KrbFastResponse struct missing; got:\n{}",
code
);
assert!(
code.contains("pub strengthen_key: Option<EncryptionKey>"),
"strengthen_key field missing; got:\n{}",
code
);
assert!(
code.contains("pub finished: Option<KrbFastFinished>"),
"finished field missing; got:\n{}",
code
);
}
fn c_impl_config() -> CImplConfig {
CImplConfig {
header_file: "test.h".to_string(),
arena_mode: false,
pattern_mode: PatternMode::Skip,
with_containing: false,
}
}
#[test]
fn test_c_explicit_tag_header_annotation() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Msg ::= SEQUENCE {
id [0] EXPLICIT INTEGER
}
END
"#;
let module = parse(input).unwrap();
let code = generate_c_with_config(&module, CCodeGenConfig::default()).unwrap();
assert!(
code.contains("SyntaInteger* id; /* [0] EXPLICIT */"),
"explicit tag annotation missing from struct field; got:\n{}",
code
);
}
#[test]
fn test_c_implicit_tag_header_annotation() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Msg ::= SEQUENCE {
data [1] IMPLICIT OCTET STRING
}
END
"#;
let module = parse(input).unwrap();
let code = generate_c_with_config(&module, CCodeGenConfig::default()).unwrap();
assert!(
code.contains("SyntaOctetString* data; /* [1] IMPLICIT */"),
"implicit tag annotation missing from struct field; got:\n{}",
code
);
}
#[test]
fn test_c_application_tag_header_annotation() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Token ::= SEQUENCE {
val [APPLICATION 3] IMPLICIT INTEGER
}
END
"#;
let module = parse(input).unwrap();
let code = generate_c_with_config(&module, CCodeGenConfig::default()).unwrap();
assert!(
code.contains("[APPLICATION 3] IMPLICIT"),
"APPLICATION tag annotation missing from struct field; got:\n{}",
code
);
}
#[test]
fn test_c_explicit_tag_impl_enters_constructed() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Msg ::= SEQUENCE {
id [0] EXPLICIT INTEGER
}
END
"#;
let module = parse(input).unwrap();
let code = generate_c_impl(&module, c_impl_config()).unwrap();
assert!(
code.contains("synta_decoder_enter_constructed"),
"explicit decode must use synta_decoder_enter_constructed; got:\n{}",
code
);
assert!(
code.contains("SyntaTagClass_ContextSpecific"),
"explicit decode must set ContextSpecific class; got:\n{}",
code
);
assert!(
code.contains("explicit_tag.number = 0"),
"explicit decode must set tag number; got:\n{}",
code
);
assert!(
code.contains("synta_decode_integer"),
"inner INTEGER still decoded; got:\n{}",
code
);
}
#[test]
fn test_c_explicit_tag_impl_starts_constructed_encode() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Msg ::= SEQUENCE {
id [0] EXPLICIT INTEGER
}
END
"#;
let module = parse(input).unwrap();
let code = generate_c_impl(&module, c_impl_config()).unwrap();
assert!(
code.contains("synta_encoder_start_constructed"),
"explicit encode must use synta_encoder_start_constructed; got:\n{}",
code
);
assert!(
code.contains("synta_encoder_end_constructed"),
"explicit encode must call synta_encoder_end_constructed; got:\n{}",
code
);
assert!(
code.contains("synta_encode_integer"),
"inner INTEGER still encoded; got:\n{}",
code
);
}
#[test]
fn test_c_implicit_tag_impl_decode_comment_and_fallback() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Msg ::= SEQUENCE {
data [1] IMPLICIT OCTET STRING
}
END
"#;
let module = parse(input).unwrap();
let code = generate_c_impl(&module, c_impl_config()).unwrap();
assert!(
code.contains("IMPLICIT"),
"implicit decode should emit IMPLICIT note; got:\n{}",
code
);
assert!(
code.contains("synta_decode_octet_string"),
"implicit decode falls back to inner type decode; got:\n{}",
code
);
assert!(
!code.contains("synta_decoder_enter_constructed"),
"implicit decode must NOT use enter_constructed; got:\n{}",
code
);
}
#[test]
fn test_c_implicit_tag_impl_encode_comment_and_fallback() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Msg ::= SEQUENCE {
data [1] IMPLICIT OCTET STRING
}
END
"#;
let module = parse(input).unwrap();
let code = generate_c_impl(&module, c_impl_config()).unwrap();
assert!(
code.contains("IMPLICIT"),
"implicit encode should emit IMPLICIT note; got:\n{}",
code
);
assert!(
code.contains("synta_encode_octet_string"),
"implicit encode falls back to inner type encode; got:\n{}",
code
);
assert!(
!code.contains("synta_encoder_start_constructed"),
"implicit encode must NOT use start_constructed; got:\n{}",
code
);
}
#[test]
fn test_c_optional_explicit_tag_peek() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Msg ::= SEQUENCE {
id [0] EXPLICIT INTEGER OPTIONAL
}
END
"#;
let module = parse(input).unwrap();
let code = generate_c_impl(&module, c_impl_config()).unwrap();
assert!(
code.contains("synta_decoder_peek_tag"),
"optional explicit field must peek tag; got:\n{}",
code
);
assert!(
code.contains("SyntaTagClass_ContextSpecific"),
"peek must check ContextSpecific class; got:\n{}",
code
);
assert!(
code.contains("tag.number == 0"),
"peek must check tag number 0; got:\n{}",
code
);
assert!(
code.contains("tag.constructed"),
"explicit tag must be constructed; got:\n{}",
code
);
}
#[test]
fn test_c_default_prototype_for_all_optional_sequence() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Config ::= SEQUENCE {
host [0] EXPLICIT UTF8String OPTIONAL,
port [1] EXPLICIT INTEGER OPTIONAL
}
END
"#;
let module = parse(input).unwrap();
let code = generate_c_with_config(&module, CCodeGenConfig::default()).unwrap();
assert!(
code.contains("Config config_default(void);"),
"_default() prototype must appear for all-optional sequence; got:\n{}",
code
);
}
#[test]
fn test_c_header_simple_sequence() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
SimpleSeq ::= SEQUENCE {
version INTEGER,
data OCTET STRING
}
END
"#;
let module = parse(input).unwrap();
let code = generate_c(&module).unwrap();
assert!(
code.contains("struct SimpleSeq {"),
"should generate C struct; got:\n{}",
code
);
assert!(
code.contains("SyntaInteger* version;"),
"INTEGER field should be SyntaInteger*; got:\n{}",
code
);
assert!(
code.contains("SyntaOctetString* data;"),
"OCTET STRING field should be SyntaOctetString*; got:\n{}",
code
);
}
#[test]
fn test_c_header_optional_field() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
OptSeq ::= SEQUENCE {
optional INTEGER OPTIONAL
}
END
"#;
let module = parse(input).unwrap();
let code = generate_c(&module).unwrap();
assert!(
code.contains("bool has_optional;"),
"OPTIONAL field should emit has_* sentinel; got:\n{}",
code
);
assert!(
code.contains("SyntaInteger* optional;"),
"OPTIONAL field should still emit the pointer field; got:\n{}",
code
);
}
#[test]
fn test_c_header_named_integer_typedef_and_defines() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Protocol ::= INTEGER { tcp(6), udp(17), sctp(132) }
END
"#;
let module = parse(input).unwrap();
let code = generate_c(&module).unwrap();
assert!(
code.contains("typedef int64_t Protocol;"),
"named INTEGER should produce typedef int64_t; got:\n{}",
code
);
assert!(
code.contains("#define PROTOCOL_TCP"),
"should have PROTOCOL_TCP #define; got:\n{}",
code
);
assert!(
code.contains("((int64_t)6)"),
"named value should be cast to int64_t; got:\n{}",
code
);
assert!(
code.contains("#define PROTOCOL_UDP"),
"should have PROTOCOL_UDP #define; got:\n{}",
code
);
assert!(
code.contains("#define PROTOCOL_SCTP"),
"should have PROTOCOL_SCTP #define; got:\n{}",
code
);
assert!(
!code.contains("enum Protocol {"),
"named INTEGER must not generate a C enum; got:\n{}",
code
);
}
#[test]
fn test_c_header_named_integer_forward_decl_is_int64() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
MsgType ::= INTEGER { asReq(10), asRep(11), tgsReq(12) }
END
"#;
let module = parse(input).unwrap();
let code = generate_c(&module).unwrap();
assert!(
!code.contains("typedef enum MsgType"),
"forward decl must not be typedef enum for named INTEGER; got:\n{}",
code
);
assert!(
code.contains("typedef int64_t MsgType;"),
"forward decl must be typedef int64_t; got:\n{}",
code
);
}
#[test]
fn test_c_impl_named_integer_decode_uses_direct_assign() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Protocol ::= INTEGER { tcp(6), udp(17) }
END
"#;
let module = parse(input).unwrap();
let code = generate_c_impl(&module, c_impl_config()).unwrap();
assert!(
code.contains("Protocol* out"),
"decode must take Protocol* (not `enum Protocol*`); got:\n{}",
code
);
assert!(
code.contains("*out = val;"),
"decode must assign *out = val without an enum cast; got:\n{}",
code
);
assert!(
!code.contains("(enum Protocol)"),
"decode must not cast to enum; got:\n{}",
code
);
}
#[test]
fn test_c_impl_named_integer_encode_uses_direct_deref() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Protocol ::= INTEGER { tcp(6), udp(17) }
END
"#;
let module = parse(input).unwrap();
let code = generate_c_impl(&module, c_impl_config()).unwrap();
assert!(
code.contains("const Protocol* value"),
"encode must take `const Protocol*` (not `const enum Protocol*`); got:\n{}",
code
);
assert!(
code.contains("synta_integer_new_i64(*value)"),
"encode must dereference Protocol* directly; got:\n{}",
code
);
}
#[test]
fn test_c_header_default_integer_comment() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Settings ::= SEQUENCE {
port INTEGER DEFAULT 8080,
timeout INTEGER DEFAULT 30
}
END
"#;
let module = parse(input).unwrap();
let code = generate_c(&module).unwrap();
assert!(
code.contains("DEFAULT 8080"),
"struct field should carry /* DEFAULT 8080 */ annotation; got:\n{}",
code
);
assert!(
code.contains("DEFAULT 30"),
"struct field should carry /* DEFAULT 30 */ annotation; got:\n{}",
code
);
assert!(
code.contains("Settings settings_default(void);"),
"_default() prototype must appear for all-defaulted sequence; got:\n{}",
code
);
}
#[test]
fn test_c_header_no_default_prototype_for_required_field() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Person ::= SEQUENCE {
name UTF8String,
age INTEGER DEFAULT 0
}
END
"#;
let module = parse(input).unwrap();
let code = generate_c(&module).unwrap();
assert!(
!code.contains("Person person_default(void);"),
"_default() must not appear when sequence has a required field; got:\n{}",
code
);
}
#[test]
fn test_c_impl_default_boolean_true_assigns() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Flags ::= SEQUENCE {
enabled BOOLEAN DEFAULT TRUE,
disabled BOOLEAN DEFAULT FALSE
}
END
"#;
let module = parse(input).unwrap();
let code = generate_c_impl(&module, c_impl_config()).unwrap();
assert!(
code.contains("flags_default"),
"_default() function must be generated; got:\n{}",
code
);
assert!(
code.contains("out.enabled = true;"),
"DEFAULT TRUE should emit explicit assignment; got:\n{}",
code
);
assert!(
!code.contains("out.disabled = false;"),
"DEFAULT FALSE must not emit a redundant false assignment; got:\n{}",
code
);
}
#[test]
fn test_c_impl_default_integer_emits_comment() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Settings ::= SEQUENCE {
port INTEGER DEFAULT 8080
}
END
"#;
let module = parse(input).unwrap();
let code = generate_c_impl(&module, c_impl_config()).unwrap();
assert!(
code.contains("DEFAULT 8080"),
"_default() must document the DEFAULT value; got:\n{}",
code
);
}
#[test]
fn test_c_impl_no_default_for_required_fields() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Simple ::= SEQUENCE {
name UTF8String,
age INTEGER
}
END
"#;
let module = parse(input).unwrap();
let code = generate_c_impl(&module, c_impl_config()).unwrap();
assert!(
!code.contains("simple_default"),
"_default() must not be generated for sequences with required fields; got:\n{}",
code
);
}
#[test]
fn test_c_header_named_bit_string_constants() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Flags ::= BIT STRING { delegFlag(0), mutualFlag(1), replayFlag(2) } (SIZE (32..MAX))
END
"#;
let module = parse(input).unwrap();
let code = generate_c(&module).unwrap();
assert!(
code.contains("#define FLAGS_DELEG_FLAG_BIT"),
"should emit FLAGS_DELEG_FLAG_BIT bit constant; got:\n{}",
code
);
assert!(
code.contains("#define FLAGS_MUTUAL_FLAG_BIT"),
"should emit FLAGS_MUTUAL_FLAG_BIT bit constant; got:\n{}",
code
);
assert!(
code.contains("#define FLAGS_REPLAY_FLAG_BIT"),
"should emit FLAGS_REPLAY_FLAG_BIT bit constant; got:\n{}",
code
);
assert!(
code.contains("typedef SyntaBitString Flags;"),
"named BIT STRING should typedef SyntaBitString; got:\n{}",
code
);
}
#[test]
fn test_c_impl_named_bit_string_decode_encode() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Flags ::= BIT STRING { delegFlag(0), mutualFlag(1) } (SIZE (32..MAX))
END
"#;
let module = parse(input).unwrap();
let code = generate_c_impl(&module, c_impl_config()).unwrap();
assert!(
code.contains("synta_decode_bit_string"),
"named bit string impl should use synta_decode_bit_string; got:\n{}",
code
);
assert!(
code.contains("synta_encode_bit_string"),
"named bit string impl should use synta_encode_bit_string; got:\n{}",
code
);
}
fn borrowed_config() -> CodeGenConfig {
CodeGenConfig {
string_type_mode: synta_codegen::StringTypeMode::Borrowed,
..Default::default()
}
}
#[test]
fn test_string_type_mode_owned_is_default() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Msg ::= SEQUENCE {
data OCTET STRING,
key BIT STRING,
label UTF8String,
code PrintableString,
host IA5String
}
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(
code.contains("pub data: OctetString"),
"default mode should emit OctetString; got:\n{}",
code
);
assert!(
code.contains("pub key: BitString"),
"default mode should emit BitString; got:\n{}",
code
);
assert!(
code.contains("pub label: Utf8String"),
"default mode should emit Utf8String; got:\n{}",
code
);
assert!(
code.contains("pub code: PrintableString"),
"default mode should emit PrintableString; got:\n{}",
code
);
assert!(
code.contains("pub host: IA5String"),
"default mode should emit IA5String; got:\n{}",
code
);
assert!(
!code.contains("pub struct Msg<'a>"),
"owned mode should not add lifetime to struct; got:\n{}",
code
);
}
#[test]
fn test_string_type_mode_borrowed_octet_and_bit() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Blob ::= SEQUENCE {
data OCTET STRING,
flags BIT STRING
}
END
"#;
let module = parse(input).unwrap();
let code = generate_with_config(&module, borrowed_config()).unwrap();
assert!(
code.contains("pub data: OctetStringRef<'a>"),
"borrowed mode should emit OctetStringRef<'a>; got:\n{}",
code
);
assert!(
code.contains("pub flags: BitStringRef<'a>"),
"borrowed mode should emit BitStringRef<'a>; got:\n{}",
code
);
assert!(
code.contains("pub struct Blob<'a>"),
"borrowed mode should add lifetime to struct; got:\n{}",
code
);
}
#[test]
fn test_string_type_mode_borrowed_utf8_printable_ia5() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Names ::= SEQUENCE {
display UTF8String,
canonical PrintableString,
hostname IA5String
}
END
"#;
let module = parse(input).unwrap();
let code = generate_with_config(&module, borrowed_config()).unwrap();
assert!(
code.contains("pub display: Utf8StringRef<'a>"),
"borrowed mode should emit Utf8StringRef<'a>; got:\n{}",
code
);
assert!(
code.contains("pub canonical: PrintableStringRef<'a>"),
"borrowed mode should emit PrintableStringRef<'a>; got:\n{}",
code
);
assert!(
code.contains("pub hostname: IA5StringRef<'a>"),
"borrowed mode should emit IA5StringRef<'a>; got:\n{}",
code
);
assert!(
code.contains("pub struct Names<'a>"),
"borrowed mode should add lifetime to struct; got:\n{}",
code
);
}
#[test]
fn test_string_type_mode_borrowed_always_owned_types() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Mixed ::= SEQUENCE {
t8 TeletexString,
bmp BMPString,
uni UniversalString,
gen GeneralString,
num NumericString,
vis VisibleString
}
END
"#;
let module = parse(input).unwrap();
let code = generate_with_config(&module, borrowed_config()).unwrap();
assert!(
code.contains("pub t8: TeletexString"),
"TeletexString should remain owned; got:\n{}",
code
);
assert!(
code.contains("pub bmp: BmpString"),
"BmpString should remain owned; got:\n{}",
code
);
assert!(
code.contains("pub uni: UniversalString"),
"UniversalString should remain owned; got:\n{}",
code
);
assert!(
code.contains("pub gen: GeneralString"),
"GeneralString should remain owned; got:\n{}",
code
);
assert!(
code.contains("pub num: NumericString"),
"NumericString should remain owned; got:\n{}",
code
);
assert!(
code.contains("pub vis: VisibleString"),
"VisibleString should remain owned; got:\n{}",
code
);
assert!(
!code.contains("pub struct Mixed<'a>"),
"always-owned string types should not introduce lifetime; got:\n{}",
code
);
}
#[test]
fn test_string_type_mode_borrowed_named_bit_string_stays_owned() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
KeyUsage ::= BIT STRING {
digitalSignature(0),
keyEncipherment(2)
} (SIZE (32..MAX))
END
"#;
let module = parse(input).unwrap();
let code = generate_with_config(&module, borrowed_config()).unwrap();
assert!(
code.contains("BitString"),
"NamedBitList should emit owned BitString even in borrowed mode; got:\n{}",
code
);
assert!(
!code.contains("BitStringRef"),
"NamedBitList should not emit BitStringRef<'a>; got:\n{}",
code
);
assert!(
!code.contains("KeyUsage<'a>"),
"NamedBitList newtype should not carry lifetime; got:\n{}",
code
);
}
#[test]
fn test_string_type_mode_borrowed_type_alias_gets_lifetime() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
MyStr ::= UTF8String
MyBlob ::= OCTET STRING
END
"#;
let module = parse(input).unwrap();
let code = generate_with_config(&module, borrowed_config()).unwrap();
assert!(
code.contains("pub type MyStr<'a> = Utf8StringRef<'a>;"),
"UTF8String alias should become Utf8StringRef<'a> in borrowed mode; got:\n{}",
code
);
assert!(
code.contains("pub type MyBlob<'a> = OctetStringRef<'a>;"),
"OCTET STRING alias should become OctetStringRef<'a> in borrowed mode; got:\n{}",
code
);
}
#[test]
fn test_string_type_mode_borrowed_lifetime_propagates_through_sequence() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Inner ::= SEQUENCE {
name UTF8String
}
Outer ::= SEQUENCE {
inner Inner,
count INTEGER
}
END
"#;
let module = parse(input).unwrap();
let code = generate_with_config(&module, borrowed_config()).unwrap();
assert!(
code.contains("pub struct Inner<'a>"),
"Inner should have lifetime in borrowed mode; got:\n{}",
code
);
assert!(
code.contains("pub struct Outer<'a>"),
"Outer should have lifetime because it contains Inner<'a>; got:\n{}",
code
);
assert!(
code.contains("pub inner: Inner<'a>"),
"Outer.inner field should be Inner<'a>; got:\n{}",
code
);
}
#[test]
fn test_format_asn1_generated_for_sequence() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
MySeq ::= SEQUENCE {
id INTEGER,
name UTF8String
}
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(
code.contains("pub fn format_asn1(&self, mode: synta::Asn1FormatMode) -> String"),
"format_asn1 method missing from Sequence output:\n{}",
code
);
assert!(
code.contains("synta::format_asn1_bytes(&bytes, mode)"),
"format_asn1_bytes call missing:\n{}",
code
);
assert!(
code.contains("impl MySeq {"),
"impl block for MySeq missing:\n{}",
code
);
}
#[test]
fn test_format_asn1_generated_for_choice() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
MyChoice ::= CHOICE {
a INTEGER,
b UTF8String
}
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(
code.contains("pub fn format_asn1(&self, mode: synta::Asn1FormatMode) -> String"),
"format_asn1 method missing from Choice output:\n{}",
code
);
assert!(
code.contains("impl MyChoice {"),
"impl block for MyChoice missing:\n{}",
code
);
}
#[test]
fn test_format_asn1_generated_for_constrained_integer() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Port ::= INTEGER (0..65535)
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(
code.contains("pub fn format_asn1(&self, mode: synta::Asn1FormatMode) -> String"),
"format_asn1 method missing from constrained integer output:\n{}",
code
);
}
#[test]
fn test_format_asn1_generated_for_enumerated() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Status ::= ENUMERATED {
ok(0),
error(1)
}
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(
code.contains("pub fn format_asn1(&self, mode: synta::Asn1FormatMode) -> String"),
"format_asn1 method missing from Enumerated output:\n{}",
code
);
assert!(
code.contains("impl Status {"),
"impl block for Status missing:\n{}",
code
);
}
#[test]
fn test_format_asn1_lifetime_sequence() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
MySeq ::= SEQUENCE {
data OCTET STRING
}
END
"#;
let module = parse(input).unwrap();
let code = generate_with_config(
&module,
CodeGenConfig {
string_type_mode: synta_codegen::StringTypeMode::Borrowed,
..Default::default()
},
)
.unwrap();
assert!(
code.contains("impl<'a> MySeq<'a> {"),
"Lifetime impl block missing for borrowed-mode sequence:\n{}",
code
);
assert!(
code.contains("pub fn format_asn1(&self, mode: synta::Asn1FormatMode) -> String"),
"format_asn1 method missing from borrowed-mode sequence:\n{}",
code
);
}
#[test]
fn test_parse_class_definition() {
let input = r#"
TestModule DEFINITIONS EXPLICIT TAGS ::= BEGIN
MY-CLASS ::= CLASS {
&id OBJECT IDENTIFIER UNIQUE,
&Value OPTIONAL,
&Params OPTIONAL
} WITH SYNTAX {
IDENTIFIER &id
[VALUE &Value]
[PARAMS &Params]
}
END
"#;
let module = parse(input).unwrap();
assert_eq!(module.definitions.len(), 1);
let def = &module.definitions[0];
assert_eq!(def.name, "MY-CLASS");
if let Type::Class(fields) = &def.ty {
assert_eq!(fields.len(), 3);
assert_eq!(fields[0].name, "id");
assert!(fields[0].unique);
assert!(!fields[0].optional);
assert_eq!(fields[1].name, "Value");
assert!(!fields[1].unique);
assert!(fields[1].optional);
} else {
panic!("Expected Type::Class, got {:?}", def.ty);
}
}
#[test]
fn test_class_definition_generates_comment_only() {
let input = r#"
TestModule DEFINITIONS EXPLICIT TAGS ::= BEGIN
MY-CLASS ::= CLASS {
&id OBJECT IDENTIFIER UNIQUE,
&Value OPTIONAL
} WITH SYNTAX {
IDENTIFIER &id
[VALUE &Value]
}
END
"#;
let module = parse(input).unwrap();
let code = generate(&module).unwrap();
assert!(
code.contains("// ASN.1 Information Object Class: MyClass"),
"Class comment missing:\n{}",
code
);
assert!(
!code.contains("pub struct MyClass"),
"Class must not generate a struct:\n{}",
code
);
assert!(
!code.contains("pub type MyClass"),
"Class must not generate a type alias:\n{}",
code
);
}
#[test]
fn test_ioc_assignment_skipped() {
let input = r#"
TestModule DEFINITIONS EXPLICIT TAGS ::= BEGIN
MY-CLASS ::= CLASS {
&id OBJECT IDENTIFIER UNIQUE
} WITH SYNTAX {
IDENTIFIER &id
}
-- IOC value assignment: should be skipped
MySet MY-CLASS ::= { ... }
-- Normal type that should still be generated
MySeq ::= SEQUENCE {
version INTEGER
}
END
"#;
let module = parse(input).unwrap();
assert_eq!(
module.definitions.len(),
2,
"definitions: {:?}",
module
.definitions
.iter()
.map(|d| &d.name)
.collect::<Vec<_>>()
);
assert_eq!(module.definitions[0].name, "MY-CLASS");
assert_eq!(module.definitions[1].name, "MySeq");
let code = generate(&module).unwrap();
assert!(
code.contains("pub struct MySeq"),
"MySeq struct missing:\n{}",
code
);
}
#[test]
fn test_real_world_rfc9629_kem_algorithm_information() {
let input = r#"
KEMAlgorithmInformation-2023
{ iso(1) identified-organization(3) dod(6) internet(1)
security(5) mechanisms(5) pkix(7) id-mod(0)
id-mod-kemAlgorithmInformation-2023(109) }
DEFINITIONS EXPLICIT TAGS ::= BEGIN
KEM-ALGORITHM ::= CLASS {
&id OBJECT IDENTIFIER UNIQUE,
&Value OPTIONAL,
&Params OPTIONAL,
¶mPresence ParamOptions DEFAULT absent,
&PublicKeySet PUBLIC-KEY OPTIONAL,
&Ukm OPTIONAL,
&ukmPresence ParamOptions DEFAULT absent,
&smimeCaps SMIME-CAPS OPTIONAL
} WITH SYNTAX {
IDENTIFIER &id
[VALUE &Value]
[PARAMS [TYPE &Params] ARE ¶mPresence]
[PUBLIC-KEYS &PublicKeySet]
[UKM [TYPE &Ukm] ARE &ukmPresence]
[SMIME-CAPS &smimeCaps]
}
END
"#;
let module = parse(input).unwrap();
assert_eq!(module.name, "KEMAlgorithmInformation-2023");
assert_eq!(module.definitions.len(), 1);
assert_eq!(module.definitions[0].name, "KEM-ALGORITHM");
assert!(matches!(module.definitions[0].ty, Type::Class(_)));
let code = generate(&module).unwrap();
assert!(code.contains("// ASN.1 Information Object Class: KemAlgorithm"));
assert!(!code.contains("pub struct KemAlgorithm"));
}
#[test]
fn test_real_world_rfc9629_cms_kemri() {
let input = r#"
CMS-KEMRecipientInfo-2023
{ iso(1) member-body(2) us(840) rsadsi(113549)
pkcs(1) pkcs-9(9) smime(16) modules(0)
id-mod-cms-kemri-2023(77) }
DEFINITIONS IMPLICIT TAGS ::= BEGIN
id-ori OBJECT IDENTIFIER ::= { 1 2 840 113549 1 9 16 13 }
id-ori-kem OBJECT IDENTIFIER ::= { id-ori 3 }
SupportedOtherRecipInfo OTHER-RECIPIENT ::= { ori-KEM, ... }
ori-KEM OTHER-RECIPIENT ::= {
KEMRecipientInfo IDENTIFIED BY id-ori-kem }
KEMRecipientInfo ::= SEQUENCE {
version INTEGER,
rid ANY,
kem ANY,
kemct OCTET STRING,
kdf ANY,
kekLength INTEGER,
ukm [0] EXPLICIT OCTET STRING OPTIONAL,
wrap ANY,
encryptedKey OCTET STRING }
KEMAlgSet KEM-ALGORITHM ::= { ... }
CMSORIforKEMOtherInfo ::= SEQUENCE {
wrap ANY,
kekLength INTEGER,
ukm [0] EXPLICIT OCTET STRING OPTIONAL }
END
"#;
let module = parse(input).unwrap();
let def_names: Vec<&str> = module.definitions.iter().map(|d| d.name.as_str()).collect();
assert!(
def_names.contains(&"KEMRecipientInfo"),
"definitions: {:?}",
def_names
);
assert!(
def_names.contains(&"CMSORIforKEMOtherInfo"),
"definitions: {:?}",
def_names
);
let code = generate(&module).unwrap();
assert!(
code.contains("pub struct KEMRecipientInfo"),
"struct missing:\n{}",
code
);
assert!(
code.contains("pub struct CMSORIforKEMOtherInfo"),
"struct missing:\n{}",
code
);
assert!(code.contains("pub version:"), "field missing:\n{}", code);
assert!(code.contains("pub kek_length:"), "field missing:\n{}", code);
}
#[test]
fn test_derive_mode_feature_gated_is_default() {
let module = parse(
r#"
TestModule DEFINITIONS ::= BEGIN
Msg ::= SEQUENCE { value INTEGER }
END
"#,
)
.unwrap();
let code = generate(&module).unwrap();
assert!(
code.contains("#[cfg_attr(feature = \"derive\", derive(Asn1Sequence))]"),
"expected feature-gated Asn1Sequence derive:\n{code}"
);
assert!(
code.contains("#[cfg(feature = \"derive\")]"),
"expected cfg-gated synta_derive import:\n{code}"
);
}
#[test]
fn test_derive_mode_always_emits_unconditional_derive() {
let module = parse(
r#"
TestModule DEFINITIONS ::= BEGIN
Msg ::= SEQUENCE {
value INTEGER,
label UTF8String OPTIONAL
}
Choice ::= CHOICE {
a INTEGER,
b BOOLEAN
}
END
"#,
)
.unwrap();
let config = CodeGenConfig {
derive_mode: DeriveMode::Always,
..Default::default()
};
let code = generate_with_config(&module, config).unwrap();
assert!(
code.contains("#[derive(Asn1Sequence)]"),
"expected unconditional Asn1Sequence:\n{code}"
);
assert!(
code.contains("#[derive(Asn1Choice)]"),
"expected unconditional Asn1Choice:\n{code}"
);
assert!(
!code.contains("cfg_attr"),
"unexpected cfg_attr in Always mode:\n{code}"
);
assert!(
!code.contains("#[cfg(feature ="),
"unexpected #[cfg(feature = in Always mode:\n{code}"
);
assert!(
code.contains("use synta_derive::"),
"synta_derive import missing:\n{code}"
);
assert!(
code.contains(" #[asn1(optional)]"),
"expected unconditional asn1(optional):\n{code}"
);
}
#[test]
fn test_derive_mode_custom_feature_name() {
let module = parse(
r#"
TestModule DEFINITIONS ::= BEGIN
Msg ::= SEQUENCE {
value INTEGER,
tag [0] INTEGER OPTIONAL
}
END
"#,
)
.unwrap();
let config = CodeGenConfig {
derive_mode: DeriveMode::Custom("asn1-derive".to_string()),
..Default::default()
};
let code = generate_with_config(&module, config).unwrap();
assert!(
code.contains("#[cfg_attr(feature = \"asn1-derive\", derive(Asn1Sequence))]"),
"expected custom feature name in derive:\n{code}"
);
assert!(
code.contains("#[cfg(feature = \"asn1-derive\")]"),
"expected custom feature name in import guard:\n{code}"
);
assert!(
code.contains("#[cfg_attr(feature = \"asn1-derive\", asn1(tag("),
"expected custom feature name in tag attr:\n{code}"
);
assert!(
code.contains("#[cfg_attr(feature = \"asn1-derive\", asn1(optional))]"),
"expected custom feature name in optional attr:\n{code}"
);
assert!(
!code.contains("feature = \"derive\""),
"unexpected default 'derive' feature name:\n{code}"
);
}
#[test]
fn test_choice_with_anonymous_sequence_variant() {
let input = r#"
TestModule DEFINITIONS IMPLICIT TAGS ::= BEGIN
PrivKey ::= CHOICE {
seed [0] OCTET STRING,
expandedKey OCTET STRING,
both SEQUENCE {
seed OCTET STRING,
expandedKey OCTET STRING
}
}
END
"#;
let module = parse(input).unwrap();
let code = gen(&module);
assert!(
code.contains("pub struct PrivKeyBoth"),
"anonymous SEQUENCE variant 'both' should generate PrivKeyBoth struct; got:\n{}",
code
);
assert!(
code.contains("pub seed:"),
"PrivKeyBoth should have a 'seed' field; got:\n{}",
code
);
assert!(
code.contains("pub expanded_key:"),
"PrivKeyBoth should have an 'expanded_key' field; got:\n{}",
code
);
assert!(
code.contains("Both(PrivKeyBoth)"),
"enum variant should reference the generated struct; got:\n{}",
code
);
assert!(
!code.contains("/* nested sequence */"),
"should not emit nested-sequence placeholder; got:\n{}",
code
);
let struct_pos = code.find("pub struct PrivKeyBoth").unwrap();
let enum_pos = code.find("pub enum PrivKey").unwrap();
assert!(
struct_pos < enum_pos,
"PrivKeyBoth struct must be defined before PrivKey enum; got:\n{}",
code
);
}
#[test]
fn test_sequence_with_anonymous_sequence_field() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Config ::= SEQUENCE {
version INTEGER,
params SEQUENCE {
x INTEGER,
y INTEGER
}
}
END
"#;
let module = parse(input).unwrap();
let code = gen(&module);
assert!(
code.contains("pub struct ConfigParams"),
"anonymous SEQUENCE field 'params' should generate ConfigParams struct; got:\n{}",
code
);
assert!(
code.contains("pub params: ConfigParams"),
"Config struct should reference ConfigParams; got:\n{}",
code
);
assert!(
!code.contains("/* nested sequence */"),
"should not emit nested-sequence placeholder; got:\n{}",
code
);
let inner_pos = code.find("pub struct ConfigParams").unwrap();
let outer_pos = code.find("pub struct Config {").unwrap();
assert!(
inner_pos < outer_pos,
"ConfigParams must be defined before Config; got:\n{}",
code
);
}
#[test]
fn test_tagged_anonymous_sequence_in_choice() {
let input = r#"
TestModule DEFINITIONS IMPLICIT TAGS ::= BEGIN
Tagged ::= CHOICE {
simple INTEGER,
complex [1] SEQUENCE {
a INTEGER,
b INTEGER
}
}
END
"#;
let module = parse(input).unwrap();
let code = gen(&module);
assert!(
code.contains("pub struct TaggedComplex"),
"tagged anonymous SEQUENCE variant should generate TaggedComplex; got:\n{}",
code
);
assert!(
code.contains("Complex(TaggedComplex)"),
"enum variant should reference the generated struct; got:\n{}",
code
);
assert!(
!code.contains("/* nested sequence */"),
"should not emit nested-sequence placeholder; got:\n{}",
code
);
}
#[test]
fn test_c_codegen_choice_with_anonymous_sequence() {
let input = r#"
TestModule DEFINITIONS IMPLICIT TAGS ::= BEGIN
PrivKey ::= CHOICE {
seed [0] OCTET STRING,
expandedKey OCTET STRING,
both SEQUENCE {
seed OCTET STRING,
expandedKey OCTET STRING
}
}
END
"#;
let module = parse(input).unwrap();
let code = generate_c(&module).unwrap();
assert!(
code.contains("struct PrivKeyBoth {"),
"anonymous SEQUENCE variant 'both' should generate struct PrivKeyBoth; got:\n{}",
code
);
assert!(
code.contains("PrivKeyBoth both;"),
"union member should be 'PrivKeyBoth both;', not void*; got:\n{}",
code
);
assert!(
!code.contains("void* both"),
"must not emit void* placeholder for anonymous SEQUENCE variant; got:\n{}",
code
);
let fwd_both = code
.find("typedef struct PrivKeyBoth PrivKeyBoth;")
.unwrap_or(usize::MAX);
let fwd_privkey = code
.find("typedef struct PrivKey PrivKey;")
.unwrap_or(usize::MAX);
assert!(
fwd_both < fwd_privkey,
"PrivKeyBoth forward decl must precede PrivKey forward decl; got:\n{}",
code
);
let struct_both = code.find("struct PrivKeyBoth {").unwrap();
let struct_privkey = code.find("struct PrivKey {").unwrap();
assert!(
struct_both < struct_privkey,
"PrivKeyBoth struct definition must precede PrivKey struct definition; got:\n{}",
code
);
}
#[test]
fn test_c_codegen_sequence_with_anonymous_sequence_field() {
let input = r#"
TestModule DEFINITIONS ::= BEGIN
Config ::= SEQUENCE {
version INTEGER,
params SEQUENCE {
x INTEGER,
y INTEGER
}
}
END
"#;
let module = parse(input).unwrap();
let code = generate_c(&module).unwrap();
assert!(
code.contains("struct ConfigParams {"),
"anonymous SEQUENCE field 'params' should generate struct ConfigParams; got:\n{}",
code
);
assert!(
code.contains("ConfigParams params;"),
"Config struct should have 'ConfigParams params;'; got:\n{}",
code
);
let struct_params = code.find("struct ConfigParams {").unwrap();
let struct_config = code
.find("struct Config {")
.unwrap_or_else(|| code.find("struct Config\n").unwrap_or(usize::MAX));
assert!(
struct_params < struct_config,
"ConfigParams must be defined before Config; got:\n{}",
code
);
}