1use std::collections::{BTreeMap, BTreeSet};
9
10use serde_json::Value;
11
12use super::{ConfigMap, ConfigMapError, DimRule, KeyFull, KeyRule, LayerSource};
13use crate::ir::{ArchShape, LayerKind};
14
15#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct MappedConfig {
18 pub arch: String,
20 pub model_type: String,
22 pub shape: ArchShape,
24 pub params: BTreeMap<String, String>,
26}
27
28pub fn map_config(map: &ConfigMap, config: &Value) -> Result<MappedConfig, ConfigMapError> {
30 let f = &map.file;
31 let root = config
32 .as_object()
33 .ok_or_else(|| ConfigMapError::Json("the top level is not an object".into()))?;
34 let model_type = root
35 .get("model_type")
36 .and_then(Value::as_str)
37 .ok_or_else(|| missing(map, "model_type"))?
38 .to_string();
39 if !f.model_types.contains(&model_type) {
40 return Err(ConfigMapError::Refused {
41 key: "model_type".into(),
42 value: json(&Value::String(model_type)),
43 why: format!("the `{}` config map serves {:?}", map.arch, f.model_types),
44 });
45 }
46 let mut params = BTreeMap::new();
47 let obj = match &f.nest {
48 Some(nest) => {
49 let inner = root
50 .get(nest)
51 .and_then(Value::as_object)
52 .ok_or_else(|| missing(map, nest))?;
53 let others: BTreeMap<&str, &Value> = root
54 .iter()
55 .filter(|(k, _)| k.as_str() != nest)
56 .map(|(k, v)| (k.as_str(), v))
57 .collect();
58 classify(map, ("", ""), &others, &f.root, &mut params)?;
59 inner
60 }
61 None => root,
62 };
63 let fields: BTreeMap<&str, &Value> = obj
64 .iter()
65 .filter(|(_, v)| !v.is_null())
66 .map(|(k, v)| (k.as_str(), v))
67 .collect();
68 let variant = f.variant.get(&model_type).cloned().unwrap_or_default();
69 let mut dims_rules = f.dims.clone();
70 dims_rules.extend(variant.dims);
71 let mut key_rules = f.keys.clone();
72 key_rules.extend(variant.keys);
73
74 let mut consumed: BTreeSet<&str> = BTreeSet::new();
75 let layer_kinds = layers(map, &fields, &mut consumed)?;
76 let dims = dims(map, &fields, &dims_rules, &mut consumed)?;
77 let rest: BTreeMap<&str, &Value> = obj
78 .iter()
79 .filter(|(k, _)| !consumed.contains(k.as_str()))
80 .map(|(k, v)| (k.as_str(), v))
81 .collect();
82 let prefix = f.nest.as_ref().map_or(String::new(), |n| format!("{n}."));
85 classify(map, (&prefix, ""), &rest, &key_rules, &mut params)?;
86 Ok(MappedConfig {
87 arch: map.arch.clone(),
88 model_type,
89 shape: ArchShape { layer_kinds, dims },
90 params,
91 })
92}
93
94fn missing(map: &ConfigMap, key: &str) -> ConfigMapError {
95 ConfigMapError::MissingKey {
96 arch: map.arch.clone(),
97 key: key.to_string(),
98 }
99}
100
101fn json(v: &Value) -> String {
102 serde_json::to_string(v).unwrap_or_else(|_| "?".into())
103}
104
105fn uint(key: &str, v: &Value) -> Result<u64, ConfigMapError> {
106 v.as_u64().ok_or_else(|| {
107 ConfigMapError::Json(format!("`{key}` = {} is not an unsigned integer", json(v)))
108 })
109}
110
111fn layers<'a>(
112 map: &ConfigMap,
113 fields: &BTreeMap<&'a str, &'a Value>,
114 consumed: &mut BTreeSet<&'a str>,
115) -> Result<Vec<LayerKind>, ConfigMapError> {
116 let l = &map.file.layers;
117 let (count_key, count_v) = fields
118 .get_key_value(l.count.as_str())
119 .ok_or_else(|| missing(map, &l.count))?;
120 consumed.insert(count_key);
121 let count = uint(&l.count, count_v)? as usize;
122 let kind = |name: &str, key: &str| {
123 LayerKind::parse(name).ok_or_else(|| {
124 ConfigMapError::Schema(format!("`{key}` maps to unknown layer kind `{name}`"))
125 })
126 };
127 let present: Vec<(&LayerSource, &'a str, &'a Value)> = l
128 .sources
129 .iter()
130 .filter_map(|s| {
131 fields
132 .get_key_value(s.key.as_str())
133 .map(|(k, v)| (s, *k, *v))
134 })
135 .collect();
136 for (_, k, _) in &present {
137 consumed.insert(k);
138 }
139 let kinds = match (present.as_slice(), &l.uniform) {
140 ([], Some(u)) => vec![kind(u, "uniform")?; count],
141 ([], None) => {
142 let keys: Vec<&str> = l.sources.iter().map(|s| s.key.as_str()).collect();
143 return Err(missing(map, &keys.join(" | ")));
144 }
145 ([(src, key, v)], None) => from_source(src, key, v)?
146 .into_iter()
147 .map(|n| kind(&n, key))
148 .collect::<Result<_, _>>()?,
149 _ => {
150 let keys: Vec<&str> = present.iter().map(|(_, k, _)| *k).collect();
151 return Err(ConfigMapError::Refused {
152 key: keys.join(", "),
153 value: "(several)".into(),
154 why: "the layer kinds must come from exactly one source".into(),
155 });
156 }
157 };
158 if kinds.len() != count {
159 return Err(ConfigMapError::Refused {
160 key: l.count.clone(),
161 value: count.to_string(),
162 why: format!("the layer-kind source lists {} layers", kinds.len()),
163 });
164 }
165 Ok(kinds)
166}
167
168fn from_source(src: &LayerSource, key: &str, v: &Value) -> Result<Vec<String>, ConfigMapError> {
169 let refuse = |value: String, why: String| ConfigMapError::Refused {
170 key: key.to_string(),
171 value,
172 why,
173 };
174 if let Some(list) = v.as_array() {
175 list.iter()
176 .map(|e| {
177 let s = e.as_str().unwrap_or_default();
178 src.values.get(s).cloned().ok_or_else(|| {
179 refuse(
180 json(e),
181 format!(
182 "a layer kind this arch maps: {:?}",
183 src.values.keys().collect::<Vec<_>>()
184 ),
185 )
186 })
187 })
188 .collect()
189 } else if let Some(s) = v.as_str() {
190 s.chars()
191 .map(|c| {
192 src.chars.get(&c.to_string()).cloned().ok_or_else(|| {
193 refuse(
194 format!("'{c}'"),
195 format!(
196 "pattern letters this arch maps: {:?}",
197 src.chars.keys().collect::<Vec<_>>()
198 ),
199 )
200 })
201 })
202 .collect()
203 } else {
204 Err(ConfigMapError::Json(format!(
205 "`{key}` is neither a list nor a pattern string"
206 )))
207 }
208}
209
210fn dims<'a>(
211 map: &ConfigMap,
212 fields: &BTreeMap<&'a str, &'a Value>,
213 rules: &BTreeMap<String, DimRule>,
214 consumed: &mut BTreeSet<&'a str>,
215) -> Result<BTreeMap<String, u64>, ConfigMapError> {
216 let mut out = BTreeMap::new();
217 let mut deferred = Vec::new();
218 for (name, rule) in rules {
219 let full = match rule {
220 DimRule::Key(k) => super::DimFull {
221 key: Some(k.clone()),
222 bool: false,
223 bool_present: false,
224 constant: None,
225 or_dim: None,
226 or_div: None,
227 default: None,
228 allowed: None,
229 },
230 DimRule::Full(f) => f.clone(),
231 };
232 if let Some(c) = full.constant {
233 out.insert(name.clone(), c);
234 continue;
235 }
236 let key = full
237 .key
238 .as_deref()
239 .ok_or_else(|| ConfigMapError::Schema(format!("dim `{name}` has no key or const")))?;
240 if full.bool_present {
241 let present = fields.get_key_value(key);
242 if let Some((k, _)) = present {
243 consumed.insert(k);
244 }
245 out.insert(name.clone(), u64::from(present.is_some()));
246 continue;
247 }
248 if let Some((k, v)) = fields.get_key_value(key) {
249 consumed.insert(k);
250 let value = match (full.bool, v.as_bool()) {
251 (true, Some(b)) => u64::from(b),
252 _ => uint(key, v)?,
253 };
254 if let Some(allowed) = &full.allowed
255 && !allowed.contains(&value)
256 {
257 return Err(ConfigMapError::Refused {
258 key: key.to_string(),
259 value: value.to_string(),
260 why: format!("the circuit models only {allowed:?}"),
261 });
262 }
263 out.insert(name.clone(), value);
264 } else if let Some(d) = full.default {
265 out.insert(name.clone(), d);
266 } else if full.or_dim.is_some() || full.or_div.is_some() {
267 deferred.push((name.clone(), full));
268 } else {
269 return Err(missing(map, key));
270 }
271 }
272 for (name, full) in deferred {
273 let get = |d: &str| {
274 out.get(d).copied().ok_or_else(|| {
275 ConfigMapError::Schema(format!("dim `{name}` falls back to unmapped dim `{d}`"))
276 })
277 };
278 let value = if let Some(d) = &full.or_dim {
279 get(d)?
280 } else if let Some([a, b]) = &full.or_div {
281 let (a, b) = (get(a)?, get(b)?);
282 if b == 0 || a % b != 0 {
283 return Err(ConfigMapError::Refused {
284 key: full.key.clone().unwrap_or_default(),
285 value: "(absent)".into(),
286 why: format!("its fallback {a} / {b} is not exact"),
287 });
288 }
289 a / b
290 } else {
291 unreachable!("deferred only with a fallback")
292 };
293 out.insert(name, value);
294 }
295 Ok(out)
296}
297
298fn to_json(v: &toml::Value) -> Value {
299 serde_json::to_value(v).unwrap_or(Value::Null)
300}
301
302fn classify(
305 map: &ConfigMap,
306 prefix: (&str, &str),
307 obj: &BTreeMap<&str, &Value>,
308 rules: &BTreeMap<String, KeyRule>,
309 params: &mut BTreeMap<String, String>,
310) -> Result<(), ConfigMapError> {
311 let (path_prefix, name_prefix) = prefix;
312 for (k, v) in obj {
313 if !v.is_null() && !rules.contains_key(*k) {
314 return Err(ConfigMapError::UnmappedKey {
315 arch: map.arch.clone(),
316 key: format!("{path_prefix}{k}"),
317 });
318 }
319 }
320 for (key, rule) in rules {
321 let path = format!("{path_prefix}{key}");
322 let name = format!("{name_prefix}{key}");
323 let v = obj.get(key.as_str()).copied().filter(|v| !v.is_null());
324 match rule {
325 KeyRule::Word(w) if w == "ignore" => {}
326 KeyRule::Word(w) if w == "param" => {
327 if let Some(v) = v {
328 params.insert(name, json(v));
329 }
330 }
331 KeyRule::Word(w) => {
332 return Err(ConfigMapError::Schema(format!(
333 "`{path}`: unknown rule `{w}` (ignore | param | a table)"
334 )));
335 }
336 KeyRule::Full(f) => apply_full(map, (&path, &name), v, f, params)?,
337 }
338 }
339 Ok(())
340}
341
342fn apply_full(
343 map: &ConfigMap,
344 (path, own): (&str, &str),
345 v: Option<&Value>,
346 f: &KeyFull,
347 params: &mut BTreeMap<String, String>,
348) -> Result<(), ConfigMapError> {
349 if f.ignore.is_some() {
350 return Ok(());
351 }
352 if let Some(why) = &f.refuse {
353 return match v {
354 Some(v) => Err(ConfigMapError::Refused {
355 key: path.to_string(),
356 value: json(v),
357 why: why.clone(),
358 }),
359 None => Ok(()),
360 };
361 }
362 let name = match &f.param {
363 Some(toml::Value::String(s)) => Some(s.clone()),
364 Some(toml::Value::Boolean(true)) => Some(own.to_string()),
365 Some(other) => {
366 return Err(ConfigMapError::Schema(format!(
367 "`{path}`: `param` is {other}, want a name or true"
368 )));
369 }
370 None => None,
371 };
372 if let Some(sub) = &f.object {
373 let Some(v) = v else {
374 return Ok(());
375 };
376 let o = v
377 .as_object()
378 .ok_or_else(|| ConfigMapError::Json(format!("`{path}` is not an object")))?;
379 let fields: BTreeMap<&str, &Value> = o.iter().map(|(k, v)| (k.as_str(), v)).collect();
380 let names = format!("{}.", name.unwrap_or_else(|| own.to_string()));
381 return classify(map, (&format!("{path}."), &names), &fields, sub, params);
382 }
383 let value = match (v, &f.default) {
384 (Some(v), _) => Some(v.clone()),
385 (None, Some(d)) => Some(to_json(d)),
386 (None, None) => None,
387 };
388 let Some(mut value) = value else {
389 return Ok(());
390 };
391 if let Some(s) = value.as_str()
392 && let Some(r) = f.values.get(s)
393 {
394 value = Value::String(r.clone());
395 }
396 if let Some(req) = &f.require {
397 let allowed: Vec<Value> = match req {
398 toml::Value::Array(a) => a.iter().map(to_json).collect(),
399 one => vec![to_json(one)],
400 };
401 if !allowed.contains(&value) {
402 return Err(ConfigMapError::Refused {
403 key: path.to_string(),
404 value: json(&value),
405 why: f.why.clone().unwrap_or_else(|| {
406 format!("the circuit models only {}", json(&Value::Array(allowed)))
407 }),
408 });
409 }
410 }
411 if let Some(n) = name {
412 params.insert(n, json(&value));
413 }
414 Ok(())
415}