1use std::string::{String, ToString};
2use std::vec::Vec;
3use std::format;
4
5use super::pool::DynamicPool;
6use super::fixed_bits;
7use super::codec;
8
9pub enum Value {
15 Mapping(Vec<(String, Value)>),
16 Scalar(String),
17 Null,
18}
19
20pub struct ParsedManifest {
23 pub file_key_idx: u16,
24}
25
26pub fn parse(
36 filename: &str,
37 root: Value,
38 dynamic: &mut DynamicPool,
39 keys: &mut Vec<u64>,
40 values: &mut Vec<[u64; 2]>,
41 path_map: &mut Vec<Vec<u16>>,
42 children_map: &mut Vec<Vec<u16>>,
43) -> Result<ParsedManifest, String> {
44 let Value::Mapping(mapping) = root else {
45 return Err("DSL root must be a mapping".to_string());
46 };
47
48 let dyn_idx = dynamic.intern(filename);
50 let mut file_record = fixed_bits::new();
51 file_record = fixed_bits::set(file_record, fixed_bits::K_OFFSET_DYNAMIC, fixed_bits::K_MASK_DYNAMIC, dyn_idx as u64);
52 let file_idx = keys.len() as u16;
53 keys.push(file_record);
54
55 let mut child_indices: Vec<u16> = Vec::new();
57 for (key_str, value) in &mapping {
58 let child_idx = traverse_field_key(key_str, value, filename, &[], dynamic, keys, values, path_map, children_map)?;
59 child_indices.push(child_idx);
60 }
61
62 let file_record = keys[file_idx as usize];
64 let file_record = match child_indices.len() {
65 0 => file_record,
66 1 => fixed_bits::set(file_record, fixed_bits::K_OFFSET_CHILD, fixed_bits::K_MASK_CHILD, child_indices[0] as u64),
67 _ => {
68 let children_idx = children_map.len() as u16;
69 children_map.push(child_indices);
70 let r = fixed_bits::set(file_record, fixed_bits::K_OFFSET_HAS_CHILDREN, fixed_bits::K_MASK_HAS_CHILDREN, 1);
71 fixed_bits::set(r, fixed_bits::K_OFFSET_CHILD, fixed_bits::K_MASK_CHILD, children_idx as u64)
72 }
73 };
74 keys[file_idx as usize] = file_record;
75
76 Ok(ParsedManifest { file_key_idx: file_idx })
77}
78
79fn traverse_field_key(
82 key_str: &str,
83 value: &Value,
84 filename: &str,
85 ancestors: &[&str],
86 dynamic: &mut DynamicPool,
87 keys: &mut Vec<u64>,
88 values: &mut Vec<[u64; 2]>,
89 path_map: &mut Vec<Vec<u16>>,
90 children_map: &mut Vec<Vec<u16>>,
91) -> Result<u16, String> {
92 let dyn_idx = dynamic.intern(key_str);
93 let mut record = fixed_bits::new();
94 record = fixed_bits::set(record, fixed_bits::K_OFFSET_ROOT, fixed_bits::K_MASK_ROOT, fixed_bits::ROOT_NULL);
95 record = fixed_bits::set(record, fixed_bits::K_OFFSET_DYNAMIC, fixed_bits::K_MASK_DYNAMIC, dyn_idx as u64);
96
97 let key_idx = keys.len() as u16;
98 keys.push(record);
99
100 let mut current: Vec<&str> = ancestors.to_vec();
101 current.push(key_str);
102
103 if let Value::Mapping(mapping) = value {
104 let mut child_indices: Vec<u16> = Vec::new();
105 let mut meta_indices: Vec<u16> = Vec::new();
106
107 for (k_str, v) in mapping {
108 if k_str.starts_with('_') {
109 let meta_idx = traverse_meta_key(k_str, v, filename, ¤t, dynamic, keys, values, path_map, children_map)?;
110 meta_indices.push(meta_idx);
111 } else {
112 let child_idx = traverse_field_key(k_str, v, filename, ¤t, dynamic, keys, values, path_map, children_map)?;
113 child_indices.push(child_idx);
114 }
115 }
116
117 let all_children: Vec<u16> = child_indices.iter()
118 .chain(meta_indices.iter())
119 .copied()
120 .collect();
121
122 let record = keys[key_idx as usize];
123 let record = match all_children.len() {
124 0 => record,
125 1 => fixed_bits::set(record, fixed_bits::K_OFFSET_CHILD, fixed_bits::K_MASK_CHILD, all_children[0] as u64),
126 _ => {
127 let children_idx = children_map.len() as u16;
128 children_map.push(all_children);
129 let r = fixed_bits::set(record, fixed_bits::K_OFFSET_HAS_CHILDREN, fixed_bits::K_MASK_HAS_CHILDREN, 1);
130 fixed_bits::set(r, fixed_bits::K_OFFSET_CHILD, fixed_bits::K_MASK_CHILD, children_idx as u64)
131 }
132 };
133 keys[key_idx as usize] = record;
134 } else {
135 let val_idx = build_yaml_value(value, filename, ancestors, dynamic, values, path_map)?;
137 let record = keys[key_idx as usize];
138 let record = fixed_bits::set(record, fixed_bits::K_OFFSET_IS_LEAF, fixed_bits::K_MASK_IS_LEAF, 1);
139 let record = fixed_bits::set(record, fixed_bits::K_OFFSET_CHILD, fixed_bits::K_MASK_CHILD, val_idx as u64);
140 keys[key_idx as usize] = record;
141 }
142
143 Ok(key_idx)
144}
145
146fn traverse_meta_key(
148 key_str: &str,
149 value: &Value,
150 filename: &str,
151 ancestors: &[&str],
152 dynamic: &mut DynamicPool,
153 keys: &mut Vec<u64>,
154 values: &mut Vec<[u64; 2]>,
155 path_map: &mut Vec<Vec<u16>>,
156 children_map: &mut Vec<Vec<u16>>,
157) -> Result<u16, String> {
158 let root_val = codec::root_encode(key_str);
159
160 let mut record = fixed_bits::new();
161 record = fixed_bits::set(record, fixed_bits::K_OFFSET_ROOT, fixed_bits::K_MASK_ROOT, root_val);
162
163 let key_idx = keys.len() as u16;
164 keys.push(record);
165
166 if let Value::Mapping(mapping) = value {
167 let mut child_indices: Vec<u16> = Vec::new();
168
169 for (k_str, v) in mapping {
170 let child_idx = traverse_prop_key(k_str, v, filename, ancestors, dynamic, keys, values, path_map, children_map)?;
171 child_indices.push(child_idx);
172 }
173
174 let record = keys[key_idx as usize];
175 let record = match child_indices.len() {
176 0 => record,
177 1 => fixed_bits::set(record, fixed_bits::K_OFFSET_CHILD, fixed_bits::K_MASK_CHILD, child_indices[0] as u64),
178 _ => {
179 let children_idx = children_map.len() as u16;
180 children_map.push(child_indices);
181 let r = fixed_bits::set(record, fixed_bits::K_OFFSET_HAS_CHILDREN, fixed_bits::K_MASK_HAS_CHILDREN, 1);
182 fixed_bits::set(r, fixed_bits::K_OFFSET_CHILD, fixed_bits::K_MASK_CHILD, children_idx as u64)
183 }
184 };
185 keys[key_idx as usize] = record;
186 }
187
188 Ok(key_idx)
189}
190
191fn traverse_prop_key(
193 key_str: &str,
194 value: &Value,
195 filename: &str,
196 ancestors: &[&str],
197 dynamic: &mut DynamicPool,
198 keys: &mut Vec<u64>,
199 values: &mut Vec<[u64; 2]>,
200 path_map: &mut Vec<Vec<u16>>,
201 children_map: &mut Vec<Vec<u16>>,
202) -> Result<u16, String> {
203 let (prop_val, client_val) = if key_str == "client" {
204 (fixed_bits::PROP_NULL, codec::client_encode(
205 match value { Value::Scalar(s) => s.as_str(), _ => "" }
206 ))
207 } else {
208 (codec::prop_encode(key_str), fixed_bits::CLIENT_NULL)
209 };
210
211 let mut record = fixed_bits::new();
212 record = fixed_bits::set(record, fixed_bits::K_OFFSET_PROP, fixed_bits::K_MASK_PROP, prop_val);
213 record = fixed_bits::set(record, fixed_bits::K_OFFSET_CLIENT, fixed_bits::K_MASK_CLIENT, client_val);
214
215 if key_str == "type" {
216 let type_val = codec::type_encode(
217 match value { Value::Scalar(s) => s.as_str(), _ => "" }
218 );
219 record = fixed_bits::set(record, fixed_bits::K_OFFSET_TYPE, fixed_bits::K_MASK_TYPE, type_val);
220 }
221
222 let key_idx = keys.len() as u16;
223 keys.push(record);
224
225 if key_str == "map" {
226 if let Value::Mapping(mapping) = value {
227 let mut child_indices: Vec<u16> = Vec::new();
228 for (k_str, v) in mapping {
229 let child_idx = traverse_map_key(k_str, v, filename, ancestors, dynamic, keys, values, path_map)?;
230 child_indices.push(child_idx);
231 }
232 let record = keys[key_idx as usize];
233 let record = match child_indices.len() {
234 0 => record,
235 1 => fixed_bits::set(record, fixed_bits::K_OFFSET_CHILD, fixed_bits::K_MASK_CHILD, child_indices[0] as u64),
236 _ => {
237 let children_idx = children_map.len() as u16;
238 children_map.push(child_indices);
239 let r = fixed_bits::set(record, fixed_bits::K_OFFSET_HAS_CHILDREN, fixed_bits::K_MASK_HAS_CHILDREN, 1);
240 fixed_bits::set(r, fixed_bits::K_OFFSET_CHILD, fixed_bits::K_MASK_CHILD, children_idx as u64)
241 }
242 };
243 keys[key_idx as usize] = record;
244 }
245 } else if key_str != "client" {
246 let val_idx = build_yaml_value(value, filename, ancestors, dynamic, values, path_map)?;
247 let record = keys[key_idx as usize];
248 let record = fixed_bits::set(record, fixed_bits::K_OFFSET_IS_LEAF, fixed_bits::K_MASK_IS_LEAF, 1);
249 let record = fixed_bits::set(record, fixed_bits::K_OFFSET_CHILD, fixed_bits::K_MASK_CHILD, val_idx as u64);
250 keys[key_idx as usize] = record;
251 }
252
253 Ok(key_idx)
254}
255
256fn traverse_map_key(
258 key_str: &str,
259 value: &Value,
260 filename: &str,
261 ancestors: &[&str],
262 dynamic: &mut DynamicPool,
263 keys: &mut Vec<u64>,
264 values: &mut Vec<[u64; 2]>,
265 path_map: &mut Vec<Vec<u16>>,
266) -> Result<u16, String> {
267 let qualified = build_qualified_path(filename, ancestors, key_str);
268 let seg_indices: Vec<u16> = qualified.split('.')
269 .map(|seg| dynamic.intern(seg))
270 .collect();
271 let path_idx = path_map.len() as u16;
272 path_map.push(seg_indices);
273
274 let mut record = fixed_bits::new();
275 record = fixed_bits::set(record, fixed_bits::K_OFFSET_IS_PATH, fixed_bits::K_MASK_IS_PATH, 1);
276 record = fixed_bits::set(record, fixed_bits::K_OFFSET_DYNAMIC, fixed_bits::K_MASK_DYNAMIC, path_idx as u64);
277
278 let val_idx = build_yaml_value(value, filename, ancestors, dynamic, values, path_map)?;
279 record = fixed_bits::set(record, fixed_bits::K_OFFSET_IS_LEAF, fixed_bits::K_MASK_IS_LEAF, 1);
280 record = fixed_bits::set(record, fixed_bits::K_OFFSET_CHILD, fixed_bits::K_MASK_CHILD, val_idx as u64);
281
282 let key_idx = keys.len() as u16;
283 keys.push(record);
284 Ok(key_idx)
285}
286
287fn build_yaml_value(
289 value: &Value,
290 filename: &str,
291 ancestors: &[&str],
292 dynamic: &mut DynamicPool,
293 values: &mut Vec<[u64; 2]>,
294 path_map: &mut Vec<Vec<u16>>,
295) -> Result<u16, String> {
296 let s = match value {
297 Value::Scalar(s) => s.clone(),
298 Value::Null => return Ok(0),
299 Value::Mapping(_) => return Err("unexpected mapping as scalar value".to_string()),
300 };
301
302 let tokens = split_template(&s);
303 if tokens.len() > 6 {
304 return Err(format!("value '{}' has {} tokens, max 6", s, tokens.len()));
305 }
306 let is_template = tokens.len() > 1;
307
308 let mut vo = [0u64; 2];
309
310 if is_template {
311 vo[0] = fixed_bits::set(vo[0], fixed_bits::V_OFFSET_IS_TEMPLATE, fixed_bits::V_MASK_IS_TEMPLATE, 1);
312 }
313
314 const TOKEN_OFFSETS: [(u32, u32); 6] = [
315 (fixed_bits::V_OFFSET_T0_IS_PATH, fixed_bits::V_OFFSET_T0_DYNAMIC),
316 (fixed_bits::V_OFFSET_T1_IS_PATH, fixed_bits::V_OFFSET_T1_DYNAMIC),
317 (fixed_bits::V_OFFSET_T2_IS_PATH, fixed_bits::V_OFFSET_T2_DYNAMIC),
318 (fixed_bits::V_OFFSET_T3_IS_PATH, fixed_bits::V_OFFSET_T3_DYNAMIC),
319 (fixed_bits::V_OFFSET_T4_IS_PATH, fixed_bits::V_OFFSET_T4_DYNAMIC),
320 (fixed_bits::V_OFFSET_T5_IS_PATH, fixed_bits::V_OFFSET_T5_DYNAMIC),
321 ];
322
323 for (i, token) in tokens.iter().enumerate().take(6) {
324 let dyn_idx = if token.is_path {
325 let qualified = qualify_path(&token.text, filename, ancestors);
326 let seg_indices: Vec<u16> = qualified.split('.')
327 .map(|seg| dynamic.intern(seg))
328 .collect();
329 let path_idx = path_map.len() as u16;
330 path_map.push(seg_indices);
331 path_idx
332 } else {
333 dynamic.intern(&token.text)
334 };
335
336 let word = if i < 3 { 0 } else { 1 };
337 let (off_is_path, off_dynamic) = TOKEN_OFFSETS[i];
338 vo[word] = fixed_bits::set(vo[word], off_is_path, fixed_bits::V_MASK_IS_PATH, token.is_path as u64);
339 vo[word] = fixed_bits::set(vo[word], off_dynamic, fixed_bits::V_MASK_DYNAMIC, dyn_idx as u64);
340 }
341
342 let val_idx = values.len() as u16;
343 values.push(vo);
344 Ok(val_idx)
345}
346
347
348struct Token {
350 text: String,
351 is_path: bool,
352}
353
354fn split_template(s: &str) -> Vec<Token> {
357 let mut tokens = Vec::new();
358 let mut rest = s;
359
360 loop {
361 if let Some(start) = rest.find("${") {
362 if start > 0 {
363 tokens.push(Token { text: rest[..start].to_string(), is_path: false });
364 }
365 rest = &rest[start + 2..];
366 if let Some(end) = rest.find('}') {
367 tokens.push(Token { text: rest[..end].to_string(), is_path: true });
368 rest = &rest[end + 1..];
369 } else {
370 tokens.push(Token { text: rest.to_string(), is_path: false });
371 break;
372 }
373 } else {
374 if !rest.is_empty() {
375 tokens.push(Token { text: rest.to_string(), is_path: false });
376 }
377 break;
378 }
379 }
380
381 if tokens.is_empty() {
382 tokens.push(Token { text: s.to_string(), is_path: false });
383 }
384
385 tokens
386}
387
388fn qualify_path(path: &str, filename: &str, ancestors: &[&str]) -> String {
390 if path.contains('.') {
391 return path.to_string();
392 }
393 if ancestors.is_empty() {
394 format!("{}.{}", filename, path)
395 } else {
396 format!("{}.{}.{}", filename, ancestors.join("."), path)
397 }
398}
399
400fn build_qualified_path(filename: &str, ancestors: &[&str], key_str: &str) -> String {
402 if ancestors.is_empty() {
403 format!("{}.{}", filename, key_str)
404 } else {
405 format!("{}.{}.{}", filename, ancestors.join("."), key_str)
406 }
407}
408
409#[cfg(test)]
410mod tests {
411 use super::*;
412 use crate::fixed_bits;
413 use std::vec::Vec;
414
415 fn make_vecs() -> (DynamicPool, Vec<u64>, Vec<[u64; 2]>, Vec<Vec<u16>>, Vec<Vec<u16>>) {
416 (DynamicPool::new(), vec![0], vec![[0, 0]], vec![vec![]], vec![vec![]])
417 }
418
419 fn s(v: &str) -> Value { Value::Scalar(v.to_string()) }
420 fn m(pairs: Vec<(&str, Value)>) -> Value {
421 Value::Mapping(pairs.into_iter().map(|(k, v)| (k.to_string(), v)).collect())
422 }
423
424 #[test]
427 fn test_split_template_static() {
428 let tokens = split_template("literal");
429 assert_eq!(tokens.len(), 1);
430 assert!(!tokens[0].is_path);
431 assert_eq!(tokens[0].text, "literal");
432 }
433
434 #[test]
435 fn test_split_template_path_only() {
436 let tokens = split_template("${connection.tenant}");
437 assert_eq!(tokens.len(), 1);
438 assert!(tokens[0].is_path);
439 assert_eq!(tokens[0].text, "connection.tenant");
440 }
441
442 #[test]
443 fn test_split_template_mixed() {
444 let tokens = split_template("user:${session.id}");
445 assert_eq!(tokens.len(), 2);
446 assert!(!tokens[0].is_path);
447 assert_eq!(tokens[0].text, "user:");
448 assert!(tokens[1].is_path);
449 assert_eq!(tokens[1].text, "session.id");
450 }
451
452 #[test]
455 fn test_qualify_path_absolute() {
456 assert_eq!(qualify_path("connection.common", "cache", &["user"]), "connection.common");
457 }
458
459 #[test]
460 fn test_qualify_path_relative() {
461 assert_eq!(qualify_path("org_id", "cache", &["user"]), "cache.user.org_id");
462 }
463
464 #[test]
465 fn test_qualify_path_relative_no_ancestors() {
466 assert_eq!(qualify_path("org_id", "cache", &[]), "cache.org_id");
467 }
468
469 #[test]
472 fn test_field_key_root_is_null() {
473 let (mut dynamic, mut keys, mut values, mut path_map, mut children_map) = make_vecs();
474 let root = m(vec![("foo", m(vec![]))]);
475 let pm = parse("f", root, &mut dynamic, &mut keys, &mut values, &mut path_map, &mut children_map).unwrap();
476
477 let file_rec = keys[pm.file_key_idx as usize];
478 let child_idx = fixed_bits::get(file_rec, fixed_bits::K_OFFSET_CHILD, fixed_bits::K_MASK_CHILD) as usize;
479 assert_eq!(fixed_bits::get(keys[child_idx], fixed_bits::K_OFFSET_ROOT, fixed_bits::K_MASK_ROOT), fixed_bits::ROOT_NULL);
480 }
481
482 #[test]
485 fn test_meta_key_root_bits() {
486 let (mut dynamic, mut keys, mut values, mut path_map, mut children_map) = make_vecs();
487 let root = m(vec![("foo", m(vec![
488 ("_state", m(vec![("type", s("integer"))])),
489 ("_load", m(vec![("client", s("InMemory")), ("key", s("k"))])),
490 ("_store", m(vec![("client", s("InMemory")), ("key", s("k"))])),
491 ]))]);
492 parse("f", root, &mut dynamic, &mut keys, &mut values, &mut path_map, &mut children_map).unwrap();
493
494 let roots: Vec<u64> = keys.iter().map(|&r| fixed_bits::get(r, fixed_bits::K_OFFSET_ROOT, fixed_bits::K_MASK_ROOT)).collect();
495 assert!(roots.contains(&fixed_bits::ROOT_STATE));
496 assert!(roots.contains(&fixed_bits::ROOT_LOAD));
497 assert!(roots.contains(&fixed_bits::ROOT_STORE));
498 }
499
500 #[test]
503 fn test_type_encoding() {
504 let (mut dynamic, mut keys, mut values, mut path_map, mut children_map) = make_vecs();
505 let root = m(vec![("foo", m(vec![
506 ("_state", m(vec![("type", s("integer"))])),
507 ]))]);
508 parse("f", root, &mut dynamic, &mut keys, &mut values, &mut path_map, &mut children_map).unwrap();
509
510 let types: Vec<u64> = keys.iter().map(|&r| fixed_bits::get(r, fixed_bits::K_OFFSET_TYPE, fixed_bits::K_MASK_TYPE)).collect();
511 assert!(types.contains(&fixed_bits::TYPE_I64));
512 }
513
514 #[test]
517 fn test_client_encoding() {
518 let (mut dynamic, mut keys, mut values, mut path_map, mut children_map) = make_vecs();
519 let root = m(vec![("foo", m(vec![
520 ("_store", m(vec![("client", s("KVS")), ("key", s("k")), ("ttl", s("3600"))])),
521 ]))]);
522 parse("f", root, &mut dynamic, &mut keys, &mut values, &mut path_map, &mut children_map).unwrap();
523
524 let clients: Vec<u64> = keys.iter().map(|&r| fixed_bits::get(r, fixed_bits::K_OFFSET_CLIENT, fixed_bits::K_MASK_CLIENT)).collect();
525 assert!(clients.contains(&fixed_bits::CLIENT_KVS));
526 }
527
528 #[test]
531 fn test_template_value() {
532 let (mut dynamic, mut keys, mut values, mut path_map, mut children_map) = make_vecs();
533 let root = m(vec![("foo", m(vec![
534 ("_store", m(vec![("client", s("KVS")), ("key", s("foo:${session.id}"))])),
535 ]))]);
536 parse("f", root, &mut dynamic, &mut keys, &mut values, &mut path_map, &mut children_map).unwrap();
537
538 let has_template = values.iter().any(|&vo| fixed_bits::get(vo[0], fixed_bits::V_OFFSET_IS_TEMPLATE, fixed_bits::V_MASK_IS_TEMPLATE) == 1);
539 assert!(has_template);
540 assert!(path_map.len() > 1);
541 }
542
543 #[test]
546 fn test_map_key_path_expansion() {
547 let (mut dynamic, mut keys, mut values, mut path_map, mut children_map) = make_vecs();
548 let root = m(vec![("foo", m(vec![
549 ("_load", m(vec![
550 ("client", s("Env")),
551 ("map", m(vec![("host", s("DB_HOST")), ("port", s("DB_PORT"))])),
552 ])),
553 ]))]);
554 parse("f", root, &mut dynamic, &mut keys, &mut values, &mut path_map, &mut children_map).unwrap();
555
556 let has_path = keys.iter().any(|&r| fixed_bits::get(r, fixed_bits::K_OFFSET_IS_PATH, fixed_bits::K_MASK_IS_PATH) == 1);
558 assert!(has_path);
559 }
560
561 #[test]
564 fn test_two_files_unique_indices() {
565 let (mut dynamic, mut keys, mut values, mut path_map, mut children_map) = make_vecs();
566 let a = m(vec![("x", m(vec![]))]);
567 let b = m(vec![("y", m(vec![]))]);
568 let pm_a = parse("a", a, &mut dynamic, &mut keys, &mut values, &mut path_map, &mut children_map).unwrap();
569 let pm_b = parse("b", b, &mut dynamic, &mut keys, &mut values, &mut path_map, &mut children_map).unwrap();
570
571 assert_ne!(pm_a.file_key_idx, pm_b.file_key_idx);
572
573 let dyn_a = fixed_bits::get(keys[pm_a.file_key_idx as usize], fixed_bits::K_OFFSET_DYNAMIC, fixed_bits::K_MASK_DYNAMIC) as u16;
574 let dyn_b = fixed_bits::get(keys[pm_b.file_key_idx as usize], fixed_bits::K_OFFSET_DYNAMIC, fixed_bits::K_MASK_DYNAMIC) as u16;
575 assert_eq!(dynamic.get(dyn_a), Some("a"));
576 assert_eq!(dynamic.get(dyn_b), Some("b"));
577 }
578
579 #[test]
582 fn test_root_must_be_mapping() {
583 let (mut dynamic, mut keys, mut values, mut path_map, mut children_map) = make_vecs();
584 assert!(parse("f", s("bad"), &mut dynamic, &mut keys, &mut values, &mut path_map, &mut children_map).is_err());
585 }
586}