1use std::collections::BTreeSet;
12use std::fmt::Write as _;
13
14use zerodds_idl::ast::types::{
15 BinaryOp, BitmaskDecl, BitsetDecl, CaseLabel, ConstDecl, ConstExpr, ConstType, ConstrTypeDecl,
16 Declarator, Definition, EnumDef, ExceptDecl, FloatingType, Identifier, IntegerType, Literal,
17 LiteralKind, Member, ModuleDef, PrimitiveType, ScopedName, SequenceType, Specification,
18 StringType, StructDcl, StructDef, SwitchTypeSpec, TypeDecl, TypeSpec, TypedefDecl, UnaryOp,
19 UnionDcl, UnionDef,
20};
21use zerodds_idl::semantics::annotations::{
22 BuiltinAnnotation, ExtensibilityKind, lower_annotations,
23};
24
25use crate::error::{IdlPythonError, Result};
26
27#[derive(Debug, Clone, Default)]
29pub struct PythonGenOptions {
30 pub header_comment: Option<String>,
33}
34
35thread_local! {
36 static ENUM_VALUES: std::cell::RefCell<std::collections::BTreeMap<String, i64>> =
41 const { std::cell::RefCell::new(std::collections::BTreeMap::new()) };
42
43 static CONST_VALUES: std::cell::RefCell<std::collections::BTreeMap<String, i64>> =
46 const { std::cell::RefCell::new(std::collections::BTreeMap::new()) };
47
48 static TYPE_PATHS: std::cell::RefCell<Vec<Vec<String>>> =
56 const { std::cell::RefCell::new(Vec::new()) };
57}
58
59fn register_type_paths(defs: &[Definition], scope: &mut Vec<String>) {
64 for def in defs {
65 let name: Option<&Identifier> = match def {
66 Definition::Module(m) => {
67 scope.push(m.name.text.clone());
68 register_type_paths(&m.definitions, scope);
69 scope.pop();
70 None
71 }
72 Definition::Type(TypeDecl::Constr(ConstrTypeDecl::Struct(StructDcl::Def(s)))) => {
73 Some(&s.name)
74 }
75 Definition::Type(TypeDecl::Constr(ConstrTypeDecl::Enum(e))) => Some(&e.name),
76 Definition::Type(TypeDecl::Constr(ConstrTypeDecl::Bitmask(b))) => Some(&b.name),
77 Definition::Type(TypeDecl::Constr(ConstrTypeDecl::Bitset(b))) => Some(&b.name),
78 Definition::Type(TypeDecl::Constr(ConstrTypeDecl::Union(UnionDcl::Def(u)))) => {
79 Some(&u.name)
80 }
81 Definition::Type(TypeDecl::Typedef(td)) => {
82 for d in &td.declarators {
83 let mut path = scope.clone();
84 path.push(d.name().text.clone());
85 TYPE_PATHS.with(|t| t.borrow_mut().push(path));
86 }
87 None
88 }
89 Definition::Except(e) => Some(&e.name),
90 Definition::Const(c) => Some(&c.name),
91 _ => None,
92 };
93 if let Some(id) = name {
94 let mut path = scope.clone();
95 path.push(id.text.clone());
96 TYPE_PATHS.with(|t| t.borrow_mut().push(path));
97 }
98 }
99}
100
101fn resolve_scoped_name(name: &ScopedName, scope: &[String]) -> String {
109 let parts: Vec<String> = name.parts.iter().map(|p| p.text.clone()).collect();
110 let known: Vec<Vec<String>> = TYPE_PATHS.with(|t| t.borrow().clone());
111 for cut in (0..=scope.len()).rev() {
114 let mut candidate = scope[..cut].to_vec();
115 candidate.extend(parts.iter().cloned());
116 if known.contains(&candidate) {
117 return candidate.join("_");
118 }
119 }
120 parts.join("_")
123}
124
125fn member_is_optional(m: &Member) -> bool {
127 let Ok(lowered) = lower_annotations(&m.annotations) else {
128 return false;
129 };
130 lowered
131 .builtins
132 .iter()
133 .any(|b| matches!(b, BuiltinAnnotation::Optional))
134}
135
136fn member_explicit_id(m: &Member) -> Option<u32> {
142 let lowered = lower_annotations(&m.annotations).ok()?;
143 lowered.builtins.iter().find_map(|b| match b {
144 BuiltinAnnotation::Id(n) => Some(*n),
145 _ => None,
146 })
147}
148
149fn bitmask_bit_bound(b: &BitmaskDecl) -> u32 {
156 lower_annotations(&b.annotations)
157 .ok()
158 .and_then(|l| {
159 l.builtins.iter().find_map(|a| match a {
160 BuiltinAnnotation::BitBound(n) => Some(u32::from(*n)),
161 _ => None,
162 })
163 })
164 .unwrap_or(32)
165}
166
167fn enum_bit_bound(e: &EnumDef) -> u32 {
171 lower_annotations(&e.annotations)
172 .ok()
173 .and_then(|l| {
174 l.builtins.iter().find_map(|a| match a {
175 BuiltinAnnotation::BitBound(n) => Some(u32::from(*n)),
176 _ => None,
177 })
178 })
179 .filter(|&v| (1..=32).contains(&v))
180 .unwrap_or(32)
181}
182
183fn mutable_member_ids(s: &StructDef) -> Vec<u32> {
190 let mut ids = Vec::new();
191 let mut next: u32 = 0;
192 for m in &s.members {
193 let explicit = member_explicit_id(m);
194 for _ in &m.declarators {
195 let id = explicit.unwrap_or(next);
196 ids.push(id);
197 next = id.saturating_add(1);
198 }
199 }
200 ids
201}
202
203fn register_enum_values(defs: &[Definition]) {
207 for def in defs {
208 match def {
209 Definition::Module(m) => register_enum_values(&m.definitions),
210 Definition::Type(TypeDecl::Constr(ConstrTypeDecl::Enum(e))) => {
211 for (idx, en) in e.enumerators.iter().enumerate() {
212 ENUM_VALUES.with(|m| {
213 m.borrow_mut().insert(en.name.text.clone(), idx as i64);
214 });
215 }
216 }
217 Definition::Const(c) => {
218 if let Some(v) = eval_const_int(&c.value) {
222 CONST_VALUES.with(|m| {
223 m.borrow_mut().insert(c.name.text.clone(), v);
224 });
225 }
226 }
227 _ => {}
228 }
229 }
230}
231
232pub fn generate_python_module(spec: &Specification, opts: &PythonGenOptions) -> Result<String> {
240 ENUM_VALUES.with(|m| m.borrow_mut().clear());
241 CONST_VALUES.with(|m| m.borrow_mut().clear());
242 register_enum_values(&spec.definitions);
243 TYPE_PATHS.with(|t| t.borrow_mut().clear());
244 {
245 let mut path_scope: Vec<String> = Vec::new();
246 register_type_paths(&spec.definitions, &mut path_scope);
247 }
248 let mut imports = ImportSet::default();
249 collect_imports(&spec.definitions, &mut imports)?;
250
251 let mut out = String::new();
252 out.push_str("# SPDX-License-Identifier: Apache-2.0\n");
253 if let Some(c) = &opts.header_comment {
254 for line in c.lines() {
255 out.push_str("# ");
256 out.push_str(line);
257 out.push('\n');
258 }
259 }
260 out.push_str("# Auto-generated by `zerodds-idl-python`. Do not edit by hand.\n");
261 out.push('\n');
262
263 imports.emit(&mut out);
264
265 let mut scope: Vec<String> = Vec::new();
266 emit_definitions(&mut out, &spec.definitions, &mut scope)?;
267 Ok(out)
268}
269
270#[derive(Default)]
275struct ImportSet {
276 dataclass: bool,
277 int_enum: bool,
278 int_flag: bool,
279 typing_list: bool,
280 typing_dict: bool,
281 typing_type_alias: bool,
282 zerodds_brands: BTreeSet<&'static str>,
283 zerodds_idl_struct: bool,
284 zerodds_idl_union: bool,
285}
286
287impl ImportSet {
288 fn emit(&self, out: &mut String) {
289 if self.dataclass {
290 out.push_str("from dataclasses import dataclass\n");
291 }
292 if self.int_enum || self.int_flag {
293 out.push_str("from enum import ");
294 let mut parts: Vec<&str> = Vec::new();
295 if self.int_enum {
296 parts.push("IntEnum");
297 }
298 if self.int_flag {
299 parts.push("IntFlag");
300 }
301 out.push_str(&parts.join(", "));
302 out.push('\n');
303 }
304 if self.typing_list || self.typing_dict || self.typing_type_alias {
305 out.push_str("from typing import ");
306 let mut parts: Vec<&str> = Vec::new();
307 if self.typing_dict {
308 parts.push("Dict");
309 }
310 if self.typing_list {
311 parts.push("List");
312 }
313 if self.typing_type_alias {
314 parts.push("TypeAlias");
315 }
316 out.push_str(&parts.join(", "));
317 out.push('\n');
318 }
319 if self.zerodds_idl_struct || self.zerodds_idl_union || !self.zerodds_brands.is_empty() {
320 out.push_str("from zerodds.idl import ");
321 let mut parts: Vec<String> = Vec::new();
322 if self.zerodds_idl_struct {
323 parts.push("idl_struct".into());
324 }
325 if self.zerodds_idl_union {
326 parts.push("idl_union".into());
327 }
328 for brand in &self.zerodds_brands {
329 parts.push((*brand).to_string());
330 }
331 out.push_str(&parts.join(", "));
332 out.push('\n');
333 }
334 out.push('\n');
335 }
336}
337
338fn collect_imports(defs: &[Definition], imports: &mut ImportSet) -> Result<()> {
340 for d in defs {
341 match d {
342 Definition::Module(m) => collect_imports(&m.definitions, imports)?,
343 Definition::Type(TypeDecl::Constr(ConstrTypeDecl::Struct(StructDcl::Def(s)))) => {
344 imports.dataclass = true;
345 imports.zerodds_idl_struct = true;
346 for m in &s.members {
347 collect_type_imports(&m.type_spec, imports)?;
348 collect_member_brand_imports(m, imports);
349 }
350 }
351 Definition::Type(TypeDecl::Constr(ConstrTypeDecl::Enum(_))) => {
352 imports.int_enum = true;
353 }
354 Definition::Type(TypeDecl::Constr(ConstrTypeDecl::Bitmask(_))) => {
355 imports.int_flag = true;
356 }
357 Definition::Type(TypeDecl::Constr(ConstrTypeDecl::Bitset(_))) => {
358 imports.zerodds_brands.insert("Bitset");
363 imports.typing_type_alias = true;
364 }
365 Definition::Type(TypeDecl::Constr(ConstrTypeDecl::Union(UnionDcl::Def(u)))) => {
366 imports.zerodds_idl_union = true;
367 imports.int_enum = true;
368 collect_switch_type_imports(&u.switch_type, imports);
369 for case in &u.cases {
370 collect_type_imports(&case.element.type_spec, imports)?;
371 }
372 }
373 Definition::Type(TypeDecl::Typedef(td)) => {
374 imports.typing_type_alias = true;
375 collect_type_imports(&td.type_spec, imports)?;
376 if td
377 .declarators
378 .iter()
379 .any(|d| matches!(d, Declarator::Array(_)))
380 {
381 imports.zerodds_brands.insert("Array");
382 }
383 }
384 Definition::Except(e) => {
385 imports.dataclass = true;
386 imports.zerodds_idl_struct = true;
387 for m in &e.members {
388 collect_type_imports(&m.type_spec, imports)?;
389 collect_member_brand_imports(m, imports);
390 }
391 }
392 _ => {
393 }
398 }
399 }
400 Ok(())
401}
402
403fn collect_member_brand_imports(m: &Member, imports: &mut ImportSet) {
408 if m.declarators
409 .iter()
410 .any(|d| matches!(d, Declarator::Array(_)))
411 {
412 imports.zerodds_brands.insert("Array");
413 }
414 if member_is_optional(m) {
415 imports.zerodds_brands.insert("Optional");
416 }
417}
418
419fn collect_type_imports(ts: &TypeSpec, imports: &mut ImportSet) -> Result<()> {
421 match ts {
422 TypeSpec::Primitive(p) => {
423 imports.zerodds_brands.insert(brand_for_primitive(*p));
424 }
425 TypeSpec::String(s) => {
426 if s.bound.as_ref().and_then(eval_const_int).is_some() {
427 imports.zerodds_brands.insert(if s.wide {
428 "BoundedWString"
429 } else {
430 "BoundedString"
431 });
432 } else {
433 imports.zerodds_brands.insert(brand_for_string(s));
434 }
435 }
436 TypeSpec::Sequence(seq) => {
437 if seq.bound.as_ref().and_then(eval_const_int).is_some() {
440 imports.zerodds_brands.insert("Sequence");
441 } else {
442 imports.typing_list = true;
443 }
444 collect_type_imports(&seq.elem, imports)?;
445 }
446 TypeSpec::Scoped(_) => {
447 }
450 TypeSpec::Map(m) => {
451 if m.bound.as_ref().and_then(eval_const_int).is_some() {
453 imports.zerodds_brands.insert("Map");
454 } else {
455 imports.typing_dict = true;
456 }
457 collect_type_imports(&m.key, imports)?;
458 collect_type_imports(&m.value, imports)?;
459 }
460 TypeSpec::Fixed(_) => {
461 imports.zerodds_brands.insert("Fixed");
463 }
464 TypeSpec::Any => {
465 return Err(IdlPythonError::Unsupported(format!(
466 "type spec not yet supported in python codegen: {ts:?}"
467 )));
468 }
469 }
470 Ok(())
471}
472
473fn collect_switch_type_imports(switch: &SwitchTypeSpec, imports: &mut ImportSet) {
474 match switch {
475 SwitchTypeSpec::Integer(i) => {
476 imports.zerodds_brands.insert(brand_for_integer(*i));
477 }
478 SwitchTypeSpec::Boolean => {
479 }
482 SwitchTypeSpec::Octet => {
483 imports.zerodds_brands.insert("Octet");
484 }
485 SwitchTypeSpec::Char => {
486 imports.zerodds_brands.insert("Char");
487 }
488 SwitchTypeSpec::Scoped(_) => {
489 }
492 }
493}
494
495fn emit_definitions(out: &mut String, defs: &[Definition], scope: &mut Vec<String>) -> Result<()> {
501 for d in defs {
502 match d {
503 Definition::Module(m) => emit_module(out, m, scope)?,
504 Definition::Type(TypeDecl::Constr(ConstrTypeDecl::Struct(StructDcl::Def(s)))) => {
505 emit_struct(out, s, scope)?;
506 }
507 Definition::Type(TypeDecl::Constr(ConstrTypeDecl::Struct(StructDcl::Forward(_)))) => {
508 }
510 Definition::Type(TypeDecl::Constr(ConstrTypeDecl::Enum(e))) => {
511 emit_enum(out, e, scope)?;
512 }
513 Definition::Type(TypeDecl::Constr(ConstrTypeDecl::Bitmask(b))) => {
514 emit_bitmask(out, b, scope)?;
515 }
516 Definition::Type(TypeDecl::Constr(ConstrTypeDecl::Bitset(b))) => {
517 emit_bitset(out, b, scope)?;
518 }
519 Definition::Type(TypeDecl::Constr(ConstrTypeDecl::Union(UnionDcl::Def(u)))) => {
520 emit_union(out, u, scope)?;
521 }
522 Definition::Type(TypeDecl::Constr(ConstrTypeDecl::Union(UnionDcl::Forward(_)))) => {
523 }
525 Definition::Type(TypeDecl::Typedef(td)) => {
526 emit_typedef(out, td, scope)?;
527 }
528 Definition::Except(e) => {
529 emit_exception(out, e, scope)?;
530 }
531 Definition::Const(c) => {
532 emit_const(out, c, scope)?;
533 }
534 _ => {
535 return Err(IdlPythonError::Unsupported(format!(
536 "definition variant not yet supported: {d:?}"
537 )));
538 }
539 }
540 }
541 Ok(())
542}
543
544fn emit_module(out: &mut String, m: &ModuleDef, scope: &mut Vec<String>) -> Result<()> {
546 scope.push(m.name.text.clone());
547 emit_definitions(out, &m.definitions, scope)?;
548 scope.pop();
549 Ok(())
550}
551
552fn struct_extensibility_kwarg(s: &StructDef) -> Option<&'static str> {
558 let lowered = lower_annotations(&s.annotations).ok()?;
559 match lowered.extensibility() {
560 Some(ExtensibilityKind::Final) => None, Some(ExtensibilityKind::Appendable) => Some("appendable"),
562 Some(ExtensibilityKind::Mutable) => Some("mutable"),
563 None => Some("appendable"), }
565}
566
567fn union_extensibility_kwarg(u: &UnionDef) -> Option<&'static str> {
570 let lowered = lower_annotations(&u.annotations).ok()?;
571 match lowered.extensibility() {
572 Some(ExtensibilityKind::Final) => None,
573 Some(ExtensibilityKind::Appendable) => Some("appendable"),
574 Some(ExtensibilityKind::Mutable) => Some("mutable"),
575 None => Some("appendable"), }
577}
578
579fn emit_struct(out: &mut String, s: &StructDef, scope: &[String]) -> Result<()> {
580 let typename = qualified_typename(&s.name, scope);
581 let class_name = python_class_name(&s.name, scope);
582
583 match struct_extensibility_kwarg(s) {
584 Some("mutable") => {
585 let ids = mutable_member_ids(s);
588 let id_list = ids
589 .iter()
590 .map(u32::to_string)
591 .collect::<Vec<_>>()
592 .join(", ");
593 writeln!(
594 out,
595 "@idl_struct(typename=\"{typename}\", extensibility=\"mutable\", \
596 member_ids=[{id_list}])"
597 )
598 .ok();
599 }
600 Some(ext) => {
601 writeln!(
602 out,
603 "@idl_struct(typename=\"{typename}\", extensibility=\"{ext}\")"
604 )
605 .ok();
606 }
607 None => {
608 writeln!(out, "@idl_struct(typename=\"{typename}\")").ok();
609 }
610 }
611 writeln!(out, "@dataclass").ok();
612 if let Some(base) = &s.base {
613 let base_class = resolve_scoped_name(base, scope);
614 writeln!(out, "class {class_name}({base_class}):").ok();
615 } else {
616 writeln!(out, "class {class_name}:").ok();
617 }
618
619 if s.members.is_empty() && s.base.is_none() {
620 writeln!(out, " pass").ok();
621 } else if s.members.is_empty() {
622 writeln!(out, " pass").ok();
625 } else {
626 for m in &s.members {
627 emit_members_of(out, m, scope)?;
628 }
629 }
630 out.push('\n');
631 Ok(())
632}
633
634fn emit_exception(out: &mut String, e: &ExceptDecl, scope: &[String]) -> Result<()> {
635 let typename = qualified_typename(&e.name, scope);
636 let class_name = python_class_name(&e.name, scope);
637
638 writeln!(out, "@idl_struct(typename=\"{typename}\")").ok();
639 writeln!(out, "@dataclass").ok();
640 writeln!(out, "class {class_name}(Exception):").ok();
641
642 if e.members.is_empty() {
643 writeln!(out, " pass").ok();
644 } else {
645 for m in &e.members {
646 emit_members_of(out, m, scope)?;
647 }
648 }
649 out.push('\n');
650 Ok(())
651}
652
653fn emit_members_of(out: &mut String, m: &Member, scope: &[String]) -> Result<()> {
654 let py_type = python_type_for(&m.type_spec, scope)?;
655 let optional = member_is_optional(m);
656 for d in &m.declarators {
657 let field_name = escape_python_keyword(d.name().text.as_str());
658 let mut t = py_type.clone();
659 if let Declarator::Array(arr) = d {
660 for size in arr.sizes.iter().rev() {
666 let n = eval_const_int(size).ok_or_else(|| {
667 IdlPythonError::Unsupported(format!(
668 "array bound is not a literal integer: {size:?}"
669 ))
670 })?;
671 t = format!("Array[{t}, {n}]");
672 }
673 }
674 if optional {
677 t = format!("Optional[{t}]");
678 }
679 writeln!(out, " {field_name}: {t}").ok();
680 }
681 Ok(())
682}
683
684fn emit_enum(out: &mut String, e: &EnumDef, scope: &[String]) -> Result<()> {
685 let class_name = python_class_name(&e.name, scope);
686 writeln!(out, "class {class_name}(IntEnum):").ok();
687 if e.enumerators.is_empty() {
688 writeln!(out, " pass").ok();
689 } else {
690 for (idx, en) in e.enumerators.iter().enumerate() {
691 writeln!(out, " {} = {idx}", en.name.text).ok();
692 }
693 }
694 let bound = enum_bit_bound(e);
699 if bound != 32 {
700 writeln!(out, "{class_name}._idl_bit_bound = {bound}").ok();
701 }
702 out.push('\n');
703 Ok(())
704}
705
706fn emit_bitmask(out: &mut String, b: &BitmaskDecl, scope: &[String]) -> Result<()> {
707 let class_name = python_class_name(&b.name, scope);
708 writeln!(out, "class {class_name}(IntFlag):").ok();
709 if b.values.is_empty() {
710 writeln!(out, " pass").ok();
713 } else {
714 for (idx, v) in b.values.iter().enumerate() {
715 writeln!(out, " {} = 1 << {idx}", v.name.text).ok();
716 }
717 }
718 writeln!(
725 out,
726 "{class_name}._idl_bit_bound = {}",
727 bitmask_bit_bound(b)
728 )
729 .ok();
730 out.push('\n');
731 Ok(())
732}
733
734fn emit_bitset(out: &mut String, b: &BitsetDecl, scope: &[String]) -> Result<()> {
735 let class_name = python_class_name(&b.name, scope);
745 let total_bits: i64 = b
751 .bitfields
752 .iter()
753 .map(|bf| eval_const_int(&bf.spec.width).unwrap_or(0))
754 .sum();
755 writeln!(out, "{class_name}: TypeAlias = Bitset[{total_bits}]").ok();
756 writeln!(out).ok();
757 writeln!(out, "class {class_name}_Bits:").ok();
758 if b.bitfields.is_empty() {
759 writeln!(out, " pass").ok();
760 } else {
761 let mut shift: u64 = 0;
762 for bf in &b.bitfields {
763 let width = eval_const_int(&bf.spec.width).ok_or_else(|| {
764 IdlPythonError::Unsupported(format!(
765 "bitset width is not a literal integer: {:?}",
766 bf.spec.width
767 ))
768 })?;
769 if width <= 0 {
770 return Err(IdlPythonError::Unsupported(format!(
771 "bitset width must be > 0, got {width}"
772 )));
773 }
774 let width = width as u64;
775 let mask = if width >= 64 {
776 u64::MAX
777 } else {
778 (1u64 << width) - 1
779 };
780 if let Some(name) = &bf.name {
781 let up = name.text.to_uppercase();
782 writeln!(out, " {up}_SHIFT = {shift}").ok();
783 writeln!(out, " {up}_WIDTH = {width}").ok();
784 writeln!(out, " {up}_MASK = 0x{mask:x}").ok();
785 }
786 shift += width;
787 }
788 }
789 out.push('\n');
790 Ok(())
791}
792
793fn emit_union(out: &mut String, u: &UnionDef, scope: &[String]) -> Result<()> {
794 let typename = qualified_typename(&u.name, scope);
795 let class_name = python_class_name(&u.name, scope);
796 let discriminator_repr = switch_type_python_repr(&u.switch_type, scope);
797
798 let mut case_entries: Vec<(i64, String, String)> = Vec::new();
802 let mut default_entry: Option<(String, String)> = None;
803
804 for case in &u.cases {
805 let field_name = escape_python_keyword(case.element.declarator.name().text.as_str());
806 let py_type = python_type_for(&case.element.type_spec, scope)?;
807 for label in &case.labels {
808 match label {
809 CaseLabel::Value(expr) => {
810 let v = eval_const_int(expr).ok_or_else(|| {
811 IdlPythonError::Unsupported(format!(
812 "union case label is not a literal integer (scoped/enum-ref \
813 references will be supported in a follow-up): {expr:?}"
814 ))
815 })?;
816 case_entries.push((v, field_name.clone(), py_type.clone()));
817 }
818 CaseLabel::Default => {
819 default_entry = Some((field_name.clone(), py_type.clone()));
820 }
821 }
822 }
823 }
824
825 writeln!(out, "{class_name} = idl_union(").ok();
826 writeln!(out, " typename=\"{typename}\",").ok();
827 writeln!(out, " discriminator={discriminator_repr},").ok();
828 writeln!(out, " cases={{").ok();
829 for (label_value, field, py_type) in &case_entries {
830 writeln!(out, " {label_value}: (\"{field}\", {py_type}),").ok();
831 }
832 writeln!(out, " }},").ok();
833 if let Some((field, py_type)) = &default_entry {
834 writeln!(out, " default=(\"{field}\", {py_type}),").ok();
835 } else {
836 writeln!(out, " default=None,").ok();
837 }
838 if let Some(ext) = union_extensibility_kwarg(u) {
839 writeln!(out, " extensibility=\"{ext}\",").ok();
840 }
841 writeln!(out, ")").ok();
842 out.push('\n');
843 Ok(())
844}
845
846fn emit_typedef(out: &mut String, td: &TypedefDecl, scope: &[String]) -> Result<()> {
847 let py_type = python_type_for(&td.type_spec, scope)?;
848 for d in &td.declarators {
849 let alias_name = python_class_name(d.name(), scope);
850 match d {
851 Declarator::Simple(_) => {
852 writeln!(out, "{alias_name}: TypeAlias = {py_type}").ok();
853 }
854 Declarator::Array(arr) => {
855 let mut t = py_type.clone();
858 for size in arr.sizes.iter().rev() {
859 let n = eval_const_int(size).ok_or_else(|| {
860 IdlPythonError::Unsupported(format!(
861 "array bound is not a literal integer: {size:?}"
862 ))
863 })?;
864 t = format!("Array[{t}, {n}]");
865 }
866 writeln!(out, "{alias_name}: TypeAlias = {t}").ok();
867 }
868 }
869 }
870 out.push('\n');
871 Ok(())
872}
873
874fn emit_const(out: &mut String, c: &ConstDecl, scope: &[String]) -> Result<()> {
880 let name = python_class_name(&c.name, scope);
881 let value = render_const_value(&c.value, &c.type_, scope)?;
882 writeln!(out, "{name} = {value}").ok();
883 out.push('\n');
884 Ok(())
885}
886
887fn render_const_value(expr: &ConstExpr, ty: &ConstType, scope: &[String]) -> Result<String> {
893 if let ConstExpr::Literal(lit) = expr {
896 match lit.kind {
897 LiteralKind::Boolean => {
898 let v = lit.raw.trim().eq_ignore_ascii_case("true");
899 return Ok(if v { "True".into() } else { "False".into() });
900 }
901 LiteralKind::Floating | LiteralKind::Fixed => {
902 return Ok(lit
903 .raw
904 .trim()
905 .trim_end_matches(['f', 'F', 'd', 'D'])
906 .to_string());
907 }
908 LiteralKind::String | LiteralKind::WideString => {
909 return Ok(lit.raw.clone());
911 }
912 LiteralKind::Char | LiteralKind::WideChar => {
913 return Ok(lit.raw.clone());
914 }
915 LiteralKind::Integer => {}
916 }
917 }
918 if let Some(n) = eval_const_int(expr) {
920 return Ok(n.to_string());
921 }
922 if let ConstExpr::Scoped(s) = expr {
925 return Ok(resolve_scoped_name(s, scope));
926 }
927 Err(IdlPythonError::Unsupported(format!(
928 "const value not representable in python codegen (type {ty:?}): {expr:?}"
929 )))
930}
931
932fn python_type_for(ts: &TypeSpec, scope: &[String]) -> Result<String> {
938 match ts {
939 TypeSpec::Primitive(p) => Ok(brand_for_primitive(*p).to_string()),
940 TypeSpec::String(s) => {
941 if let Some(n) = s.bound.as_ref().and_then(eval_const_int) {
946 let brand = if s.wide {
947 "BoundedWString"
948 } else {
949 "BoundedString"
950 };
951 Ok(format!("{brand}[{n}]"))
952 } else {
953 Ok(brand_for_string(s).to_string())
954 }
955 }
956 TypeSpec::Sequence(seq) => {
957 let inner = python_type_for_seq_elem(seq, scope)?;
958 if let Some(n) = seq.bound.as_ref().and_then(eval_const_int) {
961 Ok(format!("Sequence[{inner}, {n}]"))
962 } else {
963 Ok(format!("List[{inner}]"))
964 }
965 }
966 TypeSpec::Scoped(name) => Ok(resolve_scoped_name(name, scope)),
967 TypeSpec::Map(m) => {
968 let k = python_type_for(&m.key, scope)?;
969 let v = python_type_for(&m.value, scope)?;
970 if let Some(n) = m.bound.as_ref().and_then(eval_const_int) {
973 Ok(format!("Map[{k}, {v}, {n}]"))
974 } else {
975 Ok(format!("Dict[{k}, {v}]"))
976 }
977 }
978 TypeSpec::Fixed(f) => {
979 let p = eval_const_int(&f.digits).unwrap_or(0);
982 let s = eval_const_int(&f.scale).unwrap_or(0);
983 Ok(format!("Fixed[{p}, {s}]"))
984 }
985 TypeSpec::Any => Err(IdlPythonError::Unsupported(format!(
986 "type spec not yet supported in python codegen: {ts:?}"
987 ))),
988 }
989}
990
991fn python_type_for_seq_elem(seq: &SequenceType, scope: &[String]) -> Result<String> {
993 python_type_for(&seq.elem, scope)
994}
995
996fn brand_for_primitive(p: PrimitiveType) -> &'static str {
997 match p {
998 PrimitiveType::Boolean => "Bool",
1003 PrimitiveType::Octet => "Octet",
1004 PrimitiveType::Char => "Char",
1005 PrimitiveType::WideChar => "WChar",
1006 PrimitiveType::Integer(i) => brand_for_integer(i),
1007 PrimitiveType::Floating(f) => match f {
1008 FloatingType::Float => "Float32",
1009 FloatingType::Double => "Float64",
1010 FloatingType::LongDouble => "LongDouble",
1011 },
1012 }
1013}
1014
1015fn brand_for_integer(i: IntegerType) -> &'static str {
1016 match i {
1017 IntegerType::Short | IntegerType::Int16 => "Int16",
1018 IntegerType::UShort | IntegerType::UInt16 => "UInt16",
1019 IntegerType::Long | IntegerType::Int32 => "Int32",
1020 IntegerType::ULong | IntegerType::UInt32 => "UInt32",
1021 IntegerType::LongLong | IntegerType::Int64 => "Int64",
1022 IntegerType::ULongLong | IntegerType::UInt64 => "UInt64",
1023 IntegerType::Int8 => "Int8",
1024 IntegerType::UInt8 => "UInt8",
1025 }
1026}
1027
1028fn brand_for_string(s: &StringType) -> &'static str {
1029 if s.wide { "WString" } else { "String" }
1030}
1031
1032fn switch_type_python_repr(switch: &SwitchTypeSpec, scope: &[String]) -> String {
1033 match switch {
1034 SwitchTypeSpec::Integer(i) => brand_for_integer(*i).to_string(),
1035 SwitchTypeSpec::Boolean => "bool".to_string(),
1036 SwitchTypeSpec::Octet => "Octet".to_string(),
1037 SwitchTypeSpec::Char => "Char".to_string(),
1038 SwitchTypeSpec::Scoped(s) => resolve_scoped_name(s, scope),
1039 }
1040}
1041
1042fn qualified_typename(name: &Identifier, scope: &[String]) -> String {
1043 if scope.is_empty() {
1044 name.text.clone()
1045 } else {
1046 format!("{}::{}", scope.join("::"), name.text)
1047 }
1048}
1049
1050fn python_class_name(name: &Identifier, scope: &[String]) -> String {
1051 if scope.is_empty() {
1052 name.text.clone()
1053 } else {
1054 let mut parts = scope.to_vec();
1055 parts.push(name.text.clone());
1056 parts.join("_")
1057 }
1058}
1059
1060fn escape_python_keyword(name: &str) -> String {
1061 const PYTHON_KEYWORDS: &[&str] = &[
1062 "False", "None", "True", "and", "as", "assert", "async", "await", "break", "class",
1063 "continue", "def", "del", "elif", "else", "except", "finally", "for", "from", "global",
1064 "if", "import", "in", "is", "lambda", "nonlocal", "not", "or", "pass", "raise", "return",
1065 "try", "while", "with", "yield", "match", "case",
1066 ];
1067 if PYTHON_KEYWORDS.contains(&name) {
1068 format!("{name}_")
1069 } else {
1070 name.to_string()
1071 }
1072}
1073
1074fn eval_const_int(expr: &ConstExpr) -> Option<i64> {
1080 match expr {
1081 ConstExpr::Literal(Literal {
1082 kind: LiteralKind::Integer,
1083 raw,
1084 ..
1085 }) => parse_integer_literal(raw),
1086 ConstExpr::Unary { op, operand, .. } => {
1087 let inner = eval_const_int(operand)?;
1088 match op {
1089 UnaryOp::Plus => Some(inner),
1090 UnaryOp::Minus => Some(-inner),
1091 UnaryOp::BitNot => Some(!inner),
1092 }
1093 }
1094 ConstExpr::Scoped(s) => {
1097 let name = s.parts.last()?.text.clone();
1098 ENUM_VALUES
1099 .with(|m| m.borrow().get(&name).copied())
1100 .or_else(|| CONST_VALUES.with(|m| m.borrow().get(&name).copied()))
1101 }
1102 ConstExpr::Binary { op, lhs, rhs, .. } => {
1104 let l = eval_const_int(lhs)?;
1105 let r = eval_const_int(rhs)?;
1106 match op {
1107 BinaryOp::Or => Some(l | r),
1108 BinaryOp::Xor => Some(l ^ r),
1109 BinaryOp::And => Some(l & r),
1110 BinaryOp::Shl => Some(l.checked_shl(u32::try_from(r).ok()?)?),
1111 BinaryOp::Shr => Some(l.checked_shr(u32::try_from(r).ok()?)?),
1112 BinaryOp::Add => l.checked_add(r),
1113 BinaryOp::Sub => l.checked_sub(r),
1114 BinaryOp::Mul => l.checked_mul(r),
1115 BinaryOp::Div => l.checked_div(r),
1116 BinaryOp::Mod => l.checked_rem(r),
1117 }
1118 }
1119 _ => None,
1120 }
1121}
1122
1123fn parse_integer_literal(raw: &str) -> Option<i64> {
1124 let s = raw.trim();
1125 let s = s.trim_end_matches(['L', 'l', 'u', 'U']);
1129 if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
1130 i64::from_str_radix(hex, 16).ok()
1131 } else if let Some(rest) = s.strip_prefix("-0x").or_else(|| s.strip_prefix("-0X")) {
1132 i64::from_str_radix(rest, 16).ok().map(|n| -n)
1133 } else if s.starts_with('0') && s.len() > 1 && !s.contains(|c: char| !c.is_ascii_digit()) {
1134 i64::from_str_radix(&s[1..], 8).ok()
1136 } else {
1137 s.parse::<i64>().ok()
1138 }
1139}