blueprint_engine_core/value/
structs.rs1use std::collections::HashMap;
2use std::sync::Arc;
3
4use indexmap::IndexMap;
5
6use super::Value;
7use crate::error::{BlueprintError, Result};
8
9#[derive(Debug, Clone, PartialEq)]
10pub enum TypeAnnotation {
11 Simple(String),
12 Parameterized(String, Vec<TypeAnnotation>),
13 Optional(Box<TypeAnnotation>),
14 Any,
15}
16
17impl TypeAnnotation {
18 pub fn matches(&self, value: &Value) -> bool {
19 match self {
20 TypeAnnotation::Any => true,
21 TypeAnnotation::Simple(name) => match name.as_str() {
22 "int" => matches!(value, Value::Int(_)),
23 "float" => matches!(value, Value::Float(_) | Value::Int(_)),
24 "str" => matches!(value, Value::String(_)),
25 "bool" => matches!(value, Value::Bool(_)),
26 "list" => matches!(value, Value::List(_)),
27 "dict" => matches!(value, Value::Dict(_)),
28 "tuple" => matches!(value, Value::Tuple(_)),
29 "None" | "NoneType" => matches!(value, Value::None),
30 struct_name => {
31 if let Value::StructInstance(inst) = value {
32 inst.struct_type.name == struct_name
33 } else {
34 false
35 }
36 }
37 },
38 TypeAnnotation::Parameterized(name, _params) => match name.as_str() {
39 "list" => matches!(value, Value::List(_)),
40 "dict" => matches!(value, Value::Dict(_)),
41 _ => false,
42 },
43 TypeAnnotation::Optional(inner) => matches!(value, Value::None) || inner.matches(value),
44 }
45 }
46
47 pub fn type_name(&self) -> String {
48 match self {
49 TypeAnnotation::Any => "any".to_string(),
50 TypeAnnotation::Simple(name) => name.clone(),
51 TypeAnnotation::Parameterized(name, params) => {
52 let param_strs: Vec<String> = params.iter().map(|p| p.type_name()).collect();
53 format!("{}[{}]", name, param_strs.join(", "))
54 }
55 TypeAnnotation::Optional(inner) => format!("{}?", inner.type_name()),
56 }
57 }
58}
59
60#[derive(Debug, Clone)]
61pub struct StructField {
62 pub name: String,
63 pub typ: TypeAnnotation,
64 pub default: Option<Value>,
65}
66
67#[derive(Debug, Clone)]
68pub struct StructType {
69 pub name: String,
70 pub fields: Vec<StructField>,
71}
72
73impl StructType {
74 pub fn instantiate(
75 &self,
76 args: Vec<Value>,
77 kwargs: HashMap<String, Value>,
78 ) -> Result<StructInstance> {
79 let mut field_values: IndexMap<String, Value> = IndexMap::new();
80
81 let mut positional_idx = 0;
82 for field in &self.fields {
83 let value = if let Some(v) = kwargs.get(&field.name) {
84 v.clone()
85 } else if positional_idx < args.len() {
86 let v = args[positional_idx].clone();
87 positional_idx += 1;
88 v
89 } else if let Some(default) = &field.default {
90 default.clone()
91 } else {
92 return Err(BlueprintError::ArgumentError {
93 message: format!(
94 "{}() missing required argument: '{}'",
95 self.name, field.name
96 ),
97 });
98 };
99
100 if !field.typ.matches(&value) {
101 return Err(BlueprintError::TypeError {
102 expected: format!(
103 "{} for field '{}' in {}()",
104 field.typ.type_name(),
105 field.name,
106 self.name
107 ),
108 actual: value.type_name().to_string(),
109 });
110 }
111
112 field_values.insert(field.name.clone(), value);
113 }
114
115 if positional_idx < args.len() {
116 return Err(BlueprintError::ArgumentError {
117 message: format!(
118 "{}() takes {} positional arguments but {} were given",
119 self.name,
120 self.fields.len(),
121 args.len()
122 ),
123 });
124 }
125
126 for key in kwargs.keys() {
127 if !self.fields.iter().any(|f| &f.name == key) {
128 return Err(BlueprintError::ArgumentError {
129 message: format!("{}() got unexpected keyword argument '{}'", self.name, key),
130 });
131 }
132 }
133
134 Ok(StructInstance {
135 struct_type: Arc::new(self.clone()),
136 fields: field_values,
137 })
138 }
139}
140
141#[derive(Debug, Clone)]
142pub struct StructInstance {
143 pub struct_type: Arc<StructType>,
144 pub fields: IndexMap<String, Value>,
145}
146
147impl StructInstance {
148 pub fn get_field(&self, name: &str) -> Option<Value> {
149 self.fields.get(name).cloned()
150 }
151
152 pub fn to_display_string(&self) -> String {
153 let field_strs: Vec<String> = self
154 .struct_type
155 .fields
156 .iter()
157 .map(|f| {
158 let val = self
159 .fields
160 .get(&f.name)
161 .map(|v| v.repr())
162 .unwrap_or_default();
163 format!("{}={}", f.name, val)
164 })
165 .collect();
166 format!("{}({})", self.struct_type.name, field_strs.join(", "))
167 }
168}