1use alloc::{
7 boxed::Box,
8 string::{String, ToString},
9 sync::Arc,
10 vec::Vec,
11};
12
13use super::ImportedNamespace;
14use crate::{
15 compat::HashMap,
16 error::TemplateError,
17 types::{VarDecl, VarType},
18 value::Value,
19};
20
21pub(crate) fn join_continuation_lines(block: &str) -> Vec<String> {
24 let mut logical: Vec<String> = Vec::new();
25 for raw in block.lines() {
26 if raw.starts_with(' ') || raw.starts_with('\t') {
27 if let Some(prev) = logical.last_mut() {
29 prev.push(' ');
30 prev.push_str(raw.trim());
31 } else {
32 logical.push(raw.to_string());
33 }
34 } else {
35 logical.push(raw.to_string());
36 }
37 }
38 logical
39}
40
41pub(crate) fn parse_declarations(
46 rest: &str,
47 type_aliases: &HashMap<String, VarType>,
48 resolved_imports: &HashMap<String, ImportedNamespace>,
49 is_constant: bool,
50 available_consts: &HashMap<String, Value>,
51) -> Result<Vec<VarDecl>, TemplateError> {
52 let rest = rest.trim();
53 if rest.is_empty() {
54 return Ok(vec![]);
56 }
57
58 let inner = rest
60 .strip_prefix('[')
61 .and_then(|s| s.strip_suffix(']'))
62 .unwrap_or(rest);
63
64 let entries = if inner.contains("- ") {
67 let mut result = Vec::new();
70 for part in inner.split(" - ") {
71 let part = part.trim().strip_prefix('-').unwrap_or(part).trim();
72 if !part.is_empty() {
73 result.push(part.to_string());
74 }
75 }
76 result
77 } else {
78 split_at_depth_zero(inner)
80 .into_iter()
81 .map(ToString::to_string)
82 .collect()
83 };
84
85 let mut decls = Vec::new();
86 let mut seen_names = crate::compat::HashSet::new();
87 let mut current_consts = available_consts.clone();
88 for entry in &entries {
89 let e = entry.trim();
90 let trimmed = crate::consts::strip_string_literal(e).unwrap_or(e).trim();
91 if let Some(decl) = parse_single_declaration(
92 trimmed,
93 type_aliases,
94 resolved_imports,
95 is_constant,
96 &mut current_consts,
97 &mut seen_names,
98 )? {
99 decls.push(decl);
100 }
101 }
102
103 Ok(decls)
104}
105
106fn parse_single_declaration(
108 trimmed: &str,
109 type_aliases: &HashMap<String, VarType>,
110 resolved_imports: &HashMap<String, ImportedNamespace>,
111 is_constant: bool,
112 current_consts: &mut HashMap<String, Value>,
113 seen_names: &mut crate::compat::HashSet<String>,
114) -> Result<Option<VarDecl>, TemplateError> {
115 if trimmed.is_empty() {
116 return Ok(None);
117 }
118
119 let Some(eq_pos) = find_char_at_depth_zero(trimmed, '=') else {
121 let label = if is_constant { "constant" } else { "param" };
122 return Err(TemplateError::syntax(format!(
123 "{label} '{trimmed}' is missing a type annotation (expected 'name = type')"
124 )));
125 };
126
127 let name = trimmed[..eq_pos].trim().to_string();
128 let type_and_default = trimmed[eq_pos + 1..].trim();
129
130 if !seen_names.insert(name.clone()) {
132 let err = if is_constant {
133 crate::consts::ERR_DUPLICATE_CONST
134 } else {
135 crate::consts::ERR_DUPLICATE_PARAM
136 };
137 return Err(TemplateError::syntax(format!("{err}: '{name}'")));
138 }
139
140 if crate::consts::RESERVED_NAMES.contains(&name.as_str()) {
142 return Err(TemplateError::syntax(format!(
143 "{}: '{name}'",
144 crate::consts::ERR_RESERVED_KEYWORD
145 )));
146 }
147
148 let (type_str, default_part) =
150 if let Some(assign_pos) = find_assign_default_at_depth_zero(type_and_default) {
151 (
152 type_and_default[..assign_pos].trim(),
153 Some(type_and_default[assign_pos + 2..].trim()),
154 )
155 } else {
156 (type_and_default, None)
157 };
158
159 let var_type = parse_type_annotation(type_str, type_aliases, resolved_imports)
160 .map_err(|e| TemplateError::syntax(format!("declaration '{name}': {e}")))?;
161
162 let default_value = if let Some(dp) = default_part {
163 let default = parse_default_value_with_type(dp, &var_type, current_consts)
164 .or_else(|| resolve_const_default(dp, current_consts))
165 .ok_or_else(|| {
166 TemplateError::syntax(format!(
167 "invalid default value '{dp}' for declaration '{name}' (strings must be quoted)"
168 ))
169 })?;
170 current_consts.insert(name.clone(), default.clone());
171 Some(default)
172 } else {
173 None
174 };
175
176 if is_constant && default_value.is_none() {
178 return Err(TemplateError::syntax(format!(
179 "constant '{name}' is missing a value (expected 'name = type := value')"
180 )));
181 }
182
183 if let Some(ref default) = default_value
185 && !var_type.matches(default)
186 {
187 let label = if is_constant { "constant" } else { "param" };
188 return Err(TemplateError::syntax(format!(
189 "{label} '{name}': value has type '{}' but declared type is '{var_type}'",
190 default.type_name()
191 )));
192 }
193
194 Ok(Some(VarDecl {
195 name,
196 var_type,
197 default_value,
198 }))
199}
200
201pub(crate) fn strip_type_brackets(s: &str) -> Option<&str> {
205 if let (Some(inner), true) = (
206 s.strip_prefix(crate::consts::PAREN_OPEN),
207 s.ends_with(crate::consts::PAREN_CLOSE),
208 ) {
209 Some(&inner[..inner.len() - 1])
210 } else {
211 None
212 }
213}
214
215pub(crate) fn split_at_depth_zero(input: &str) -> Vec<&str> {
217 use crate::consts::{
218 ANGLE_CLOSE, ANGLE_OPEN, BRACE_CLOSE, BRACE_OPEN, BRACKET_CLOSE, BRACKET_OPEN, COMMA,
219 PAREN_CLOSE, PAREN_OPEN,
220 };
221 let mut entries = Vec::new();
222 let mut depth: u32 = 0;
223 let mut start = 0;
224 for (i, ch) in input.char_indices() {
225 match ch {
226 ANGLE_OPEN | BRACKET_OPEN | PAREN_OPEN | BRACE_OPEN => depth += 1,
227 ANGLE_CLOSE | BRACKET_CLOSE | PAREN_CLOSE | BRACE_CLOSE => {
228 depth = depth.saturating_sub(1);
229 }
230 COMMA if depth == 0 => {
231 entries.push(&input[start..i]);
232 start = i + 1;
233 }
234 _ => {}
235 }
236 }
237 entries.push(&input[start..]);
238 entries
239}
240
241pub(crate) fn find_char_at_depth_zero(input: &str, target: char) -> Option<usize> {
243 use crate::consts::{
244 ANGLE_CLOSE, ANGLE_OPEN, BRACE_CLOSE, BRACE_OPEN, BRACKET_CLOSE, BRACKET_OPEN, PAREN_CLOSE,
245 PAREN_OPEN,
246 };
247 let mut depth: u32 = 0;
248 for (i, ch) in input.char_indices() {
249 match ch {
250 ANGLE_OPEN | BRACKET_OPEN | PAREN_OPEN | BRACE_OPEN => depth += 1,
251 ANGLE_CLOSE | BRACKET_CLOSE | PAREN_CLOSE | BRACE_CLOSE => {
252 depth = depth.saturating_sub(1);
253 }
254 c if c == target && depth == 0 => return Some(i),
255 _ => {}
256 }
257 }
258 None
259}
260
261fn find_assign_default_at_depth_zero(input: &str) -> Option<usize> {
263 use crate::consts::{
264 ANGLE_CLOSE_BYTE, ANGLE_OPEN_BYTE, BRACE_CLOSE_BYTE, BRACE_OPEN_BYTE, BRACKET_CLOSE_BYTE,
265 BRACKET_OPEN_BYTE, COLON_BYTE, EQUALS_BYTE, PAREN_CLOSE_BYTE, PAREN_OPEN_BYTE,
266 };
267 let mut depth: u32 = 0;
268 let bytes = input.as_bytes();
269 for (i, &b) in bytes.iter().enumerate() {
270 match b {
271 ANGLE_OPEN_BYTE | BRACKET_OPEN_BYTE | PAREN_OPEN_BYTE | BRACE_OPEN_BYTE => depth += 1,
272 ANGLE_CLOSE_BYTE | BRACKET_CLOSE_BYTE | PAREN_CLOSE_BYTE | BRACE_CLOSE_BYTE => {
273 depth = depth.saturating_sub(1);
274 }
275 COLON_BYTE if depth == 0 && bytes.get(i + 1) == Some(&EQUALS_BYTE) => return Some(i),
276 _ => {}
277 }
278 }
279 None
280}
281
282fn starts_with_compound_type(s: &str, keyword: &str) -> bool {
298 if let Some(rest) = s.strip_prefix(keyword) {
299 let rest = rest.trim_start();
300 rest.starts_with(crate::consts::PAREN_OPEN)
301 } else {
302 false
303 }
304}
305
306pub fn parse_type_annotation(
311 s: &str,
312 type_aliases: &HashMap<String, VarType>,
313 resolved_imports: &HashMap<String, ImportedNamespace>,
314) -> Result<VarType, String> {
315 use crate::consts::{
316 ANGLE_OPEN, BRACKET_OPEN, ERR_COMPOUND_BRACKETS_PROHIBITED, TYPE_BOOL, TYPE_ENUM,
317 TYPE_FLOAT, TYPE_INT, TYPE_LIST, TYPE_OPTION, TYPE_STR, TYPE_STRUCT, TYPE_TMPL,
318 };
319
320 let s = crate::consts::strip_string_literal(s.trim())
321 .unwrap_or(s.trim())
322 .trim();
323
324 for kw in &[TYPE_LIST, TYPE_STRUCT, TYPE_ENUM, TYPE_TMPL, TYPE_OPTION] {
325 if let Some(rest) = s.strip_prefix(kw) {
326 let rest_trimmed = rest.trim_start();
327 if rest_trimmed.starts_with(ANGLE_OPEN) || rest_trimmed.starts_with(BRACKET_OPEN) {
328 return Err(format!(
329 "compound type '{kw}': {ERR_COMPOUND_BRACKETS_PROHIBITED}"
330 ));
331 }
332 }
333 }
334
335 if let Some(ty) = type_aliases.get(s) {
337 return Ok(ty.clone());
338 }
339
340 if let Some(dot_pos) = s.find('.') {
342 let stem = &s[..dot_pos];
343 let type_name = &s[dot_pos + 1..];
344 if let Some(ns) = resolved_imports.get(stem) {
345 if let Some(ty) = ns.type_aliases.get(type_name) {
346 return Ok(ty.clone());
347 }
348 if let Some(ty) = ns.param_types.get(type_name) {
349 return Ok(ty.clone());
350 }
351 return Err(format!("import '{stem}' has no type '{type_name}'"));
352 }
353 }
354
355 if s == TYPE_STR {
356 Ok(VarType::Str)
357 } else if s == TYPE_BOOL {
358 Ok(VarType::Bool)
359 } else if s == TYPE_INT {
360 Ok(VarType::Int)
361 } else if s == TYPE_FLOAT {
362 Ok(VarType::Float)
363 } else if starts_with_compound_type(s, TYPE_LIST) {
364 parse_compound_type_list(s, type_aliases, resolved_imports)
365 } else if starts_with_compound_type(s, TYPE_STRUCT) {
366 parse_compound_type_struct(s, type_aliases, resolved_imports)
367 } else if starts_with_compound_type(s, TYPE_ENUM) {
368 parse_enum_type(s, type_aliases, resolved_imports)
369 } else if starts_with_compound_type(s, TYPE_TMPL) {
370 parse_tmpl_type(s, type_aliases, resolved_imports)
371 } else if starts_with_compound_type(s, TYPE_OPTION) {
372 parse_option_type(s, type_aliases, resolved_imports)
373 } else {
374 Err(format!("unknown type '{s}'"))
375 }
376}
377
378fn parse_enum_type(
380 s: &str,
381 type_aliases: &HashMap<String, VarType>,
382 resolved_imports: &HashMap<String, ImportedNamespace>,
383) -> Result<VarType, String> {
384 use crate::{consts::TYPE_ENUM, types::VariantDecl};
385
386 let rest = s.strip_prefix(TYPE_ENUM).unwrap_or("").trim();
387 let Some(inner) = strip_type_brackets(rest) else {
388 return Err(format!("malformed enum type: '{s}'"));
389 };
390 let entries = split_at_depth_zero(inner);
391 let mut variants = Vec::new();
392 for entry in entries {
393 let entry = entry.trim();
394 if entry.is_empty() {
395 continue;
396 }
397 if let (Some(open_idx), Some(close_idx)) = (
398 entry.find(crate::consts::PAREN_OPEN),
399 entry.rfind(crate::consts::PAREN_CLOSE),
400 ) {
401 let name = entry[..open_idx].trim().to_string();
402 let fields_str = &entry[open_idx + 1..close_idx];
403 let fields = parse_field_declarations(fields_str, type_aliases, resolved_imports)?;
404 if fields.iter().any(|f| f.name.is_empty()) {
405 return Err(
406 "enum struct variant must use named fields (e.g. Variant(name = str))"
407 .to_string(),
408 );
409 }
410 variants.push(VariantDecl { name, fields });
411 continue;
412 }
413 variants.push(VariantDecl {
414 name: entry.to_string(),
415 fields: vec![],
416 });
417 }
418 if variants.is_empty() {
419 return Err("enum must have at least one variant".to_string());
420 }
421 for v in &variants {
423 if crate::consts::RESERVED_NAMES.contains(&v.name.as_str()) {
424 return Err(format!(
425 "enum variant name '{}' shadows a builtin type keyword",
426 v.name
427 ));
428 }
429 }
430 Ok(VarType::Enum(variants))
431}
432
433fn parse_compound_type_list(
435 s: &str,
436 type_aliases: &HashMap<String, VarType>,
437 resolved_imports: &HashMap<String, ImportedNamespace>,
438) -> Result<VarType, String> {
439 use crate::consts::TYPE_LIST;
440
441 let rest = s.strip_prefix(TYPE_LIST).unwrap_or("").trim();
442 let Some(inner) = strip_type_brackets(rest) else {
443 return Err(format!("malformed list type: '{s}'"));
444 };
445 let fields = parse_field_declarations(inner, type_aliases, resolved_imports)?;
446 if fields.is_empty() {
447 return Err("untyped list() is not allowed; must specify element type or fields (e.g., list(str) or list(name = str))".to_string());
448 }
449 if fields.len() > 1 && fields.iter().any(|f| f.name.is_empty()) {
450 return Err(
451 "list with multiple fields must use named fields (e.g. list(name = str, count = int))"
452 .to_string(),
453 );
454 }
455 let inner_trimmed = inner.trim();
458 if inner_trimmed.starts_with("struct<")
459 || inner_trimmed.starts_with("struct(")
460 || inner_trimmed.starts_with("struct[")
461 || inner_trimmed.starts_with("struct ")
462 {
463 return Err(
464 "list(struct(..)) is redundant; use named fields directly: list(name = str, count = int)"
465 .to_string(),
466 );
467 }
468 if fields.len() == 1 && fields[0].name.is_empty() {
471 if let VarType::Struct(ref struct_fields) = fields[0].var_type {
472 return Ok(VarType::List(struct_fields.clone()));
473 }
474 }
475 Ok(VarType::List(fields))
476}
477
478fn parse_compound_type_struct(
480 s: &str,
481 type_aliases: &HashMap<String, VarType>,
482 resolved_imports: &HashMap<String, ImportedNamespace>,
483) -> Result<VarType, String> {
484 use crate::consts::TYPE_STRUCT;
485
486 let rest = s.strip_prefix(TYPE_STRUCT).unwrap_or("").trim();
487 let Some(inner) = strip_type_brackets(rest) else {
488 return Err(format!("malformed struct type: '{s}'"));
489 };
490 let fields = parse_field_declarations(inner, type_aliases, resolved_imports)?;
491 if fields.is_empty() {
492 return Err(
493 "untyped struct() is not allowed; must specify fields (e.g., struct(name = str))"
494 .to_string(),
495 );
496 }
497 if fields.iter().any(|f| f.name.is_empty()) {
498 return Err(
499 "struct must use named fields (e.g. struct(name = str, count = int))".to_string(),
500 );
501 }
502 Ok(VarType::Struct(fields))
503}
504
505fn parse_tmpl_type(
507 s: &str,
508 type_aliases: &HashMap<String, VarType>,
509 resolved_imports: &HashMap<String, ImportedNamespace>,
510) -> Result<VarType, String> {
511 use crate::consts::TYPE_TMPL;
512
513 let rest = s.strip_prefix(TYPE_TMPL).unwrap_or("").trim();
514 let Some(inner) = strip_type_brackets(rest) else {
515 return Err(format!("malformed tmpl type: '{s}'"));
516 };
517 let fields = parse_field_declarations(inner, type_aliases, resolved_imports)?;
518 if fields.iter().any(|f| f.name.is_empty()) {
519 return Err("tmpl must use named fields (e.g. tmpl(name = str, count = int))".to_string());
520 }
521 Ok(VarType::Tmpl(fields))
522}
523
524fn parse_option_type(
526 s: &str,
527 type_aliases: &HashMap<String, VarType>,
528 resolved_imports: &HashMap<String, ImportedNamespace>,
529) -> Result<VarType, String> {
530 use crate::consts::TYPE_OPTION;
531
532 let rest = s.strip_prefix(TYPE_OPTION).unwrap_or("").trim();
533 let Some(inner) = strip_type_brackets(rest) else {
534 return Err(format!("malformed option type: '{s}'"));
535 };
536 let inner = inner.trim();
537 if inner.is_empty() {
538 return Err("option() requires an inner type (e.g. option(str))".to_string());
539 }
540 let inner_type = parse_type_annotation(inner, type_aliases, resolved_imports)?;
541 Ok(VarType::Option(Box::new(inner_type)))
542}
543
544fn parse_field_declarations(
546 inner: &str,
547 type_aliases: &HashMap<String, VarType>,
548 resolved_imports: &HashMap<String, ImportedNamespace>,
549) -> Result<Vec<VarDecl>, String> {
550 let entries = split_at_depth_zero(inner);
551 let mut decls = Vec::new();
552 for f in &entries {
553 let f = f.trim();
554 if f.is_empty() {
555 continue;
556 }
557 let (name, type_str) = if let Some(eq_pos) = find_char_at_depth_zero(f, '=') {
558 (f[..eq_pos].trim().to_string(), f[eq_pos + 1..].trim())
559 } else {
560 (String::new(), f)
561 };
562 let var_type = parse_type_annotation(type_str, type_aliases, resolved_imports)?;
563 decls.push(VarDecl {
564 name,
565 var_type,
566 default_value: None,
567 });
568 }
569 Ok(decls)
570}
571
572fn parse_struct_default(
578 inner: &str,
579 fields: &[VarDecl],
580 available_consts: &HashMap<String, Value>,
581) -> Value {
582 let entries = split_at_depth_zero(inner);
583 let mut map = HashMap::new();
584 for e in entries {
585 let e = e.trim();
586 if e.is_empty() {
587 continue;
588 }
589 if let Some(eq_pos) = find_char_at_depth_zero(e, '=') {
590 let key = e[..eq_pos].trim();
591 let val_str = e[eq_pos + 1..].trim();
592 let field_type = fields
593 .iter()
594 .find(|d| d.name == key)
595 .map_or(&VarType::Str, |d| &d.var_type);
596 if let Some(v) = parse_default_value_with_type(val_str, field_type, available_consts) {
597 map.insert(key.to_string(), v);
598 }
599 }
600 }
601 Value::Struct(Arc::new(map))
602}
603
604fn resolve_const_default(name: &str, available_consts: &HashMap<String, Value>) -> Option<Value> {
610 let name = name.trim();
611 if name.is_empty() {
612 return None;
613 }
614 available_consts.get(name).cloned()
615}
616
617pub(crate) fn parse_default_value_with_type(
628 s: &str,
629 var_type: &VarType,
630 available_consts: &HashMap<String, Value>,
631) -> Option<Value> {
632 let s = s.trim();
633 if s.is_empty() {
634 return None;
635 }
636
637 if s.starts_with('[') && s.ends_with(']') {
639 let inner = &s[1..s.len() - 1];
640 if inner.trim().is_empty() {
641 return Some(Value::List(Arc::new(Vec::new())));
642 }
643 let entries = split_at_depth_zero(inner);
644 let mut list = Vec::new();
645 let elem_type = match var_type {
646 VarType::List(fields) => {
647 if fields.len() == 1 && fields[0].name.is_empty() {
648 &fields[0].var_type
649 } else {
650 var_type
651 }
652 }
653 _ => var_type,
654 };
655 for e in entries {
656 if let Some(v) = parse_default_value_with_type(e, elem_type, available_consts) {
657 list.push(v);
658 }
659 }
660 return Some(Value::List(Arc::new(list)));
661 }
662
663 if s.starts_with('{') && s.ends_with('}') {
665 let inner = &s[1..s.len() - 1].trim();
666 if inner.is_empty() {
667 return match var_type {
668 VarType::Struct(_) => Some(Value::Struct(Arc::new(HashMap::new()))),
669 _ => None,
670 };
671 }
672
673 let fields = match var_type {
674 VarType::Struct(f) | VarType::List(f) => f.as_slice(),
675 _ => &[],
676 };
677 return Some(parse_struct_default(inner, fields, available_consts));
678 }
679
680 if let Some(inner) = crate::consts::strip_string_literal(s) {
682 return Some(Value::Str(inner.to_string()));
683 }
684
685 if s == crate::consts::LIT_TRUE {
687 return Some(Value::Bool(true));
688 }
689 if s == crate::consts::LIT_FALSE {
690 return Some(Value::Bool(false));
691 }
692
693 if let Ok(n) = s.parse::<i64>() {
695 return Some(Value::Int(n));
696 }
697
698 if let Ok(n) = s.parse::<f64>() {
700 return Some(Value::Float(n));
701 }
702
703 if let VarType::Option(inner) = var_type {
706 if s == crate::consts::OPTION_NONE {
707 return Some(Value::None);
708 }
709 return parse_default_value_with_type(s, inner, available_consts);
710 }
711
712 if let VarType::Enum(variants) = var_type {
714 return parse_enum_default_value(s, variants, available_consts);
715 }
716
717 if let Some(val) = resolve_const_default(s, available_consts) {
718 return Some(val);
719 }
720
721 None
724}
725
726fn parse_enum_default_value(
729 s: &str,
730 variants: &[crate::types::VariantDecl],
731 available_consts: &HashMap<String, Value>,
732) -> Option<Value> {
733 if let Some(open_pos) = s.find(crate::consts::PAREN_OPEN) {
737 if s.ends_with(crate::consts::PAREN_CLOSE) {
738 let variant_name = s[..open_pos].trim();
739 let inner = &s[open_pos + 1..s.len() - 1];
740 let variant = variants.iter().find(|v| v.name == variant_name);
742 match variant {
743 Some(v) if v.fields.is_empty() => {
744 return None; }
746 Some(v) => {
747 let entries = split_at_depth_zero(inner);
749 let mut map = HashMap::new();
750 map.insert(
751 crate::consts::ENUM_TAG_KEY.to_string(),
752 Value::Str(variant_name.to_string()),
753 );
754 for e in entries {
755 let e = e.trim();
756 if e.is_empty() {
757 continue;
758 }
759 if let Some(eq_pos) = find_char_at_depth_zero(e, '=') {
760 let key = e[..eq_pos].trim();
761 let val_str = e[eq_pos + 1..].trim();
762 let field_type = v
763 .fields
764 .iter()
765 .find(|f| f.name == key)
766 .map_or(&VarType::Str, |f| &f.var_type);
767 if let Some(val) =
768 parse_default_value_with_type(val_str, field_type, available_consts)
769 {
770 map.insert(key.to_string(), val);
771 }
772 }
773 }
774 return Some(Value::Struct(Arc::new(map)));
775 }
776 None => return None, }
778 }
779 }
780
781 let variant = variants.iter().find(|v| v.name == s);
783 match variant {
784 Some(v) if !v.fields.is_empty() => {
785 None
787 }
788 Some(_) => Some(Value::Str(s.to_string())),
789 None => None, }
791}
792
793#[cfg(test)]
794pub(crate) fn parse_default_value(s: &str) -> Option<Value> {
795 parse_default_value_with_type(s, &VarType::Str, &HashMap::new())
796}
797
798#[cfg(test)]
799mod tests {
800 use super::*;
801 use crate::{
802 compat::HashMap,
803 types::{VarDecl, VarType},
804 value::Value,
805 };
806
807 fn parse_type(s: &str) -> Result<VarType, String> {
809 let aliases = HashMap::new();
810 let imports = HashMap::new();
811 parse_type_annotation(s, &aliases, &imports)
812 }
813
814 fn parse_decls(rest: &str) -> Result<Vec<VarDecl>, crate::error::TemplateError> {
816 let aliases = HashMap::new();
817 let imports = HashMap::new();
818 let consts = HashMap::new();
819 parse_declarations(rest, &aliases, &imports, false, &consts)
820 }
821
822 fn parse_consts(rest: &str) -> Result<Vec<VarDecl>, crate::error::TemplateError> {
824 let aliases = HashMap::new();
825 let imports = HashMap::new();
826 let consts = HashMap::new();
827 parse_declarations(rest, &aliases, &imports, true, &consts)
828 }
829
830 #[test]
835 fn join_normal_lines() {
836 let block = "line1\nline2\nline3";
837 let result = join_continuation_lines(block);
838 assert_eq!(result, vec!["line1", "line2", "line3"]);
839 }
840
841 #[test]
842 fn join_indented_continuation() {
843 let block = "key:\n continued\n more";
844 let result = join_continuation_lines(block);
845 assert_eq!(result.len(), 1);
846 assert_eq!(result[0], "key: continued more");
847 }
848
849 #[test]
850 fn join_tab_continuation() {
851 let block = "key:\n\tcontinued\n\tmore";
852 let result = join_continuation_lines(block);
853 assert_eq!(result.len(), 1);
854 assert_eq!(result[0], "key: continued more");
855 }
856
857 #[test]
858 fn join_first_line_indented() {
859 let block = " indented_first\nsecond";
862 let result = join_continuation_lines(block);
863 assert_eq!(result.len(), 2);
864 assert_eq!(result[0], " indented_first");
865 assert_eq!(result[1], "second");
866 }
867
868 #[test]
869 fn join_multiple_groups() {
870 let block = "key1: val1\n continued1\nkey2: val2\n continued2";
871 let result = join_continuation_lines(block);
872 assert_eq!(result.len(), 2);
873 assert_eq!(result[0], "key1: val1 continued1");
874 assert_eq!(result[1], "key2: val2 continued2");
875 }
876
877 #[test]
878 fn join_empty_block() {
879 let result = join_continuation_lines("");
880 assert!(result.is_empty());
881 }
882
883 #[test]
884 fn join_no_continuations() {
885 let block = "a\nb\nc";
886 let result = join_continuation_lines(block);
887 assert_eq!(result, vec!["a", "b", "c"]);
888 }
889
890 #[test]
895 fn split_simple_comma() {
896 let result = split_at_depth_zero("a, b, c");
897 assert_eq!(result, vec!["a", " b", " c"]);
898 }
899
900 #[test]
901 fn split_nested_angle_brackets_preserved() {
902 let result = split_at_depth_zero("name = str, items = list<label = str, count = int>");
903 assert_eq!(result.len(), 2);
904 assert_eq!(result[0], "name = str");
905 assert_eq!(result[1], " items = list<label = str, count = int>");
906 }
907
908 #[test]
909 fn split_nested_parens() {
910 let result = split_at_depth_zero("A(x = str, y = int), B");
911 assert_eq!(result.len(), 2);
912 assert_eq!(result[0], "A(x = str, y = int)");
913 assert_eq!(result[1], " B");
914 }
915
916 #[test]
917 fn split_empty_input() {
918 let result = split_at_depth_zero("");
919 assert_eq!(result, vec![""]);
920 }
921
922 #[test]
923 fn split_single_entry() {
924 let result = split_at_depth_zero("only_one");
925 assert_eq!(result, vec!["only_one"]);
926 }
927
928 #[test]
929 fn split_nested_braces() {
930 let result = split_at_depth_zero("{a: 1, b: 2}, c");
931 assert_eq!(result.len(), 2);
932 assert_eq!(result[0], "{a: 1, b: 2}");
933 assert_eq!(result[1], " c");
934 }
935
936 #[test]
937 fn split_deeply_nested() {
938 let result = split_at_depth_zero("list<list<a = str, b = list<c = int>>>, x = bool");
939 assert_eq!(result.len(), 2);
940 assert_eq!(result[0], "list<list<a = str, b = list<c = int>>>");
941 assert_eq!(result[1], " x = bool");
942 }
943
944 #[test]
949 fn find_equals_at_depth_zero() {
950 let result = find_char_at_depth_zero("name = str", '=');
951 assert_eq!(result, Some(5));
952 }
953
954 #[test]
955 fn find_skips_inside_angle_brackets() {
956 let result = find_char_at_depth_zero("list<a = str>", '=');
957 assert_eq!(result, None, "= inside <> should not be found at depth 0");
958 }
959
960 #[test]
961 fn find_returns_none_when_not_found() {
962 let result = find_char_at_depth_zero("no_target_here", '=');
963 assert_eq!(result, None);
964 }
965
966 #[test]
967 fn find_first_occurrence_at_depth_zero() {
968 let result = find_char_at_depth_zero("a = b = c", '=');
969 assert_eq!(result, Some(2));
970 }
971
972 #[test]
973 fn find_inside_parens_skipped() {
974 let result = find_char_at_depth_zero("fn(x = 1)", '=');
975 assert_eq!(result, None);
976 }
977
978 #[test]
979 fn find_after_brackets() {
980 let result = find_char_at_depth_zero("list<a = str> = val", '=');
981 assert_eq!(result, Some(14));
982 }
983
984 #[test]
985 fn find_on_empty_input() {
986 assert_eq!(find_char_at_depth_zero("", '='), None);
987 }
988
989 #[test]
994 fn find_assign_default_basic() {
995 let result = find_assign_default_at_depth_zero("str := hello");
996 assert_eq!(result, Some(4));
997 }
998
999 #[test]
1000 fn find_assign_default_skips_inside_brackets() {
1001 let result = find_assign_default_at_depth_zero("list<str := x>");
1002 assert_eq!(result, None);
1003 }
1004
1005 #[test]
1006 fn find_assign_default_not_found() {
1007 let result = find_assign_default_at_depth_zero("str");
1008 assert_eq!(result, None);
1009 }
1010
1011 #[test]
1012 fn find_assign_default_colon_without_equals() {
1013 let result = find_assign_default_at_depth_zero("a: b");
1015 assert_eq!(result, None);
1016 }
1017
1018 #[test]
1023 fn parse_default_quoted_string() {
1024 assert_eq!(
1025 parse_default_value("\"hello\""),
1026 Some(Value::Str("hello".to_string()))
1027 );
1028 }
1029
1030 #[test]
1031 fn parse_default_single_quoted_string() {
1032 assert_eq!(
1033 parse_default_value("'world'"),
1034 Some(Value::Str("world".to_string()))
1035 );
1036 }
1037
1038 #[test]
1039 fn parse_default_integer() {
1040 assert_eq!(parse_default_value("42"), Some(Value::Int(42)));
1041 }
1042
1043 #[test]
1044 fn parse_default_negative_integer() {
1045 assert_eq!(parse_default_value("-7"), Some(Value::Int(-7)));
1046 }
1047
1048 #[test]
1049 fn parse_default_float() {
1050 assert_eq!(parse_default_value("3.125"), Some(Value::Float(3.125)));
1051 }
1052
1053 #[test]
1054 fn parse_default_bool_true() {
1055 assert_eq!(parse_default_value("true"), Some(Value::Bool(true)));
1056 }
1057
1058 #[test]
1059 fn parse_default_bool_false() {
1060 assert_eq!(parse_default_value("false"), Some(Value::Bool(false)));
1061 }
1062
1063 #[test]
1064 fn parse_default_list() {
1065 let result = parse_default_value("[1, 2, 3]").unwrap();
1066 match result {
1067 Value::List(items) => {
1068 assert_eq!(items.len(), 3);
1069 assert_eq!(items[0], Value::Int(1));
1070 assert_eq!(items[1], Value::Int(2));
1071 assert_eq!(items[2], Value::Int(3));
1072 }
1073 other => panic!("Expected List, got {other:?}"),
1074 }
1075 }
1076
1077 #[test]
1078 fn parse_default_dict() {
1079 let result = parse_default_value_with_type(
1080 "{a = 1, b = 2}",
1081 &VarType::Struct(vec![
1082 VarDecl {
1083 name: "a".into(),
1084 var_type: VarType::Int,
1085 default_value: None,
1086 },
1087 VarDecl {
1088 name: "b".into(),
1089 var_type: VarType::Int,
1090 default_value: None,
1091 },
1092 ]),
1093 &HashMap::new(),
1094 )
1095 .unwrap();
1096 match result {
1097 Value::Struct(map) => {
1098 assert_eq!(map.get("a"), Some(&Value::Int(1)));
1099 assert_eq!(map.get("b"), Some(&Value::Int(2)));
1100 }
1101 other => panic!("Expected Struct, got {other:?}"),
1102 }
1103 }
1104
1105 #[test]
1106 fn parse_default_empty_returns_none() {
1107 assert_eq!(parse_default_value(""), None);
1108 }
1109
1110 #[test]
1111 fn parse_default_whitespace_only_returns_none() {
1112 assert_eq!(parse_default_value(" "), None);
1113 }
1114
1115 #[test]
1116 fn parse_default_unquoted_string() {
1117 assert_eq!(parse_default_value("hello"), None);
1119 }
1120
1121 #[test]
1122 fn parse_default_empty_list() {
1123 let result = parse_default_value("[]").unwrap();
1124 match result {
1125 Value::List(items) => assert!(items.is_empty()),
1126 other => panic!("Expected empty List, got {other:?}"),
1127 }
1128 }
1129
1130 #[test]
1131 fn parse_default_empty_dict() {
1132 let result =
1133 parse_default_value_with_type("{}", &VarType::Struct(vec![]), &HashMap::new()).unwrap();
1134 match result {
1135 Value::Struct(map) => assert!(map.is_empty()),
1136 other => panic!("Expected empty Struct, got {other:?}"),
1137 }
1138 }
1139
1140 #[test]
1141 fn parse_default_zero() {
1142 assert_eq!(parse_default_value("0"), Some(Value::Int(0)));
1143 }
1144
1145 #[test]
1146 fn parse_default_float_zero() {
1147 assert_eq!(parse_default_value("0.0"), Some(Value::Float(0.0)));
1148 }
1149
1150 #[test]
1151 fn parse_default_nested_list() {
1152 let result = parse_default_value("[1, [2, 3]]").unwrap();
1153 match result {
1154 Value::List(items) => {
1155 assert_eq!(items.len(), 2);
1156 assert_eq!(items[0], Value::Int(1));
1157 match &items[1] {
1158 Value::List(inner) => {
1159 assert_eq!(inner.len(), 2);
1160 assert_eq!(inner[0], Value::Int(2));
1161 assert_eq!(inner[1], Value::Int(3));
1162 }
1163 other => panic!("Expected inner List, got {other:?}"),
1164 }
1165 }
1166 other => panic!("Expected List, got {other:?}"),
1167 }
1168 }
1169
1170 #[test]
1171 fn parse_default_dict_with_quoted_keys() {
1172 let result = parse_default_value_with_type(
1173 "{key = 42}",
1174 &VarType::Struct(vec![VarDecl {
1175 name: "key".into(),
1176 var_type: VarType::Int,
1177 default_value: None,
1178 }]),
1179 &HashMap::new(),
1180 )
1181 .unwrap();
1182 match result {
1183 Value::Struct(map) => {
1184 assert_eq!(map.get("key"), Some(&Value::Int(42)));
1185 }
1186 other => panic!("Expected Struct, got {other:?}"),
1187 }
1188 }
1189
1190 #[test]
1195 fn type_str() {
1196 assert_eq!(parse_type("str").unwrap(), VarType::Str);
1197 }
1198
1199 #[test]
1200 fn type_bool() {
1201 assert_eq!(parse_type("bool").unwrap(), VarType::Bool);
1202 }
1203
1204 #[test]
1205 fn type_int() {
1206 assert_eq!(parse_type("int").unwrap(), VarType::Int);
1207 }
1208
1209 #[test]
1210 fn type_float() {
1211 assert_eq!(parse_type("float").unwrap(), VarType::Float);
1212 }
1213
1214 #[test]
1215 fn type_str_with_whitespace() {
1216 assert_eq!(parse_type(" str ").unwrap(), VarType::Str);
1217 }
1218
1219 #[test]
1220 fn type_list() {
1221 let result = parse_type("list(name = str)").unwrap();
1222 match result {
1223 VarType::List(fields) => {
1224 assert_eq!(fields.len(), 1);
1225 assert_eq!(fields[0].name, "name");
1226 assert_eq!(fields[0].var_type, VarType::Str);
1227 }
1228 other => panic!("Expected List, got {other:?}"),
1229 }
1230 }
1231
1232 #[test]
1233 fn type_list_multiple_fields() {
1234 let result = parse_type("list(name = str, count = int)").unwrap();
1235 match result {
1236 VarType::List(fields) => {
1237 assert_eq!(fields.len(), 2);
1238 assert_eq!(fields[0].name, "name");
1239 assert_eq!(fields[0].var_type, VarType::Str);
1240 assert_eq!(fields[1].name, "count");
1241 assert_eq!(fields[1].var_type, VarType::Int);
1242 }
1243 other => panic!("Expected List, got {other:?}"),
1244 }
1245 }
1246
1247 #[test]
1248 fn type_struct() {
1249 let result = parse_type("struct(key = str, value = int)").unwrap();
1250 match result {
1251 VarType::Struct(fields) => {
1252 assert_eq!(fields.len(), 2);
1253 assert_eq!(fields[0].name, "key");
1254 assert_eq!(fields[0].var_type, VarType::Str);
1255 assert_eq!(fields[1].name, "value");
1256 assert_eq!(fields[1].var_type, VarType::Int);
1257 }
1258 other => panic!("Expected Struct, got {other:?}"),
1259 }
1260 }
1261
1262 #[test]
1263 fn type_enum_simple() {
1264 let result = parse_type("enum(A, B, C)").unwrap();
1265 match result {
1266 VarType::Enum(variants) => {
1267 assert_eq!(variants.len(), 3);
1268 assert_eq!(variants[0].name, "A");
1269 assert!(variants[0].fields.is_empty());
1270 assert_eq!(variants[1].name, "B");
1271 assert_eq!(variants[2].name, "C");
1272 }
1273 other => panic!("Expected Enum, got {other:?}"),
1274 }
1275 }
1276
1277 #[test]
1278 fn type_enum_with_fields() {
1279 let result = parse_type("enum(A, B(field = str))").unwrap();
1280 match result {
1281 VarType::Enum(variants) => {
1282 assert_eq!(variants.len(), 2);
1283 assert_eq!(variants[0].name, "A");
1284 assert!(variants[0].fields.is_empty());
1285 assert_eq!(variants[1].name, "B");
1286 assert_eq!(variants[1].fields.len(), 1);
1287 assert_eq!(variants[1].fields[0].name, "field");
1288 assert_eq!(variants[1].fields[0].var_type, VarType::Str);
1289 }
1290 other => panic!("Expected Enum, got {other:?}"),
1291 }
1292 }
1293
1294 #[test]
1295 fn type_tmpl() {
1296 let result = parse_type("tmpl(name = str, count = int)").unwrap();
1297 match result {
1298 VarType::Tmpl(fields) => {
1299 assert_eq!(fields.len(), 2);
1300 assert_eq!(fields[0].name, "name");
1301 assert_eq!(fields[0].var_type, VarType::Str);
1302 assert_eq!(fields[1].name, "count");
1303 assert_eq!(fields[1].var_type, VarType::Int);
1304 }
1305 other => panic!("Expected Tmpl, got {other:?}"),
1306 }
1307 }
1308
1309 #[test]
1310 fn type_unknown_errors() {
1311 let err = parse_type("garbage").unwrap_err();
1312 assert!(err.contains("unknown type"), "got: {err}");
1313 }
1314
1315 #[test]
1316 fn type_bare_list_errors() {
1317 let err = parse_type("list").unwrap_err();
1318 assert!(err.contains("unknown type"), "got: {err}");
1319 }
1320
1321 #[test]
1322 fn type_bare_struct_errors() {
1323 let err = parse_type("struct").unwrap_err();
1324 assert!(err.contains("unknown type"), "got: {err}");
1325 }
1326
1327 #[test]
1328 fn type_nested_list_in_struct() {
1329 let result = parse_type("struct(items = list(name = str))").unwrap();
1330 match result {
1331 VarType::Struct(fields) => {
1332 assert_eq!(fields.len(), 1);
1333 assert_eq!(fields[0].name, "items");
1334 match &fields[0].var_type {
1335 VarType::List(inner) => {
1336 assert_eq!(inner.len(), 1);
1337 assert_eq!(inner[0].name, "name");
1338 assert_eq!(inner[0].var_type, VarType::Str);
1339 }
1340 other => panic!("Expected inner List, got {other:?}"),
1341 }
1342 }
1343 other => panic!("Expected Struct, got {other:?}"),
1344 }
1345 }
1346
1347 #[test]
1348 fn type_alias_lookup() {
1349 let mut aliases = HashMap::new();
1350 aliases.insert("Priority".to_string(), VarType::Enum(vec![]));
1351 let imports = HashMap::new();
1352 let result = parse_type_annotation("Priority", &aliases, &imports).unwrap();
1353 assert_eq!(result, VarType::Enum(vec![]));
1354 }
1355
1356 #[test]
1357 fn type_dotted_import_lookup() {
1358 let aliases = HashMap::new();
1359 let mut imports = HashMap::new();
1360 let mut ns = ImportedNamespace::default();
1361 ns.type_aliases.insert("Severity".to_string(), VarType::Str);
1362 imports.insert("types".to_string(), ns);
1363 let result = parse_type_annotation("types.Severity", &aliases, &imports).unwrap();
1364 assert_eq!(result, VarType::Str);
1365 }
1366
1367 #[test]
1368 fn type_dotted_import_not_found() {
1369 let aliases = HashMap::new();
1370 let mut imports = HashMap::new();
1371 let ns = ImportedNamespace::default();
1372 imports.insert("types".to_string(), ns);
1373 let err = parse_type_annotation("types.Missing", &aliases, &imports).unwrap_err();
1374 assert!(err.contains("has no type"), "got: {err}");
1375 }
1376
1377 #[test]
1382 fn decls_inline_basic() {
1383 let decls = parse_decls("[name = str, count = int]").unwrap();
1384 assert_eq!(decls.len(), 2);
1385 assert_eq!(decls[0].name, "name");
1386 assert_eq!(decls[0].var_type, VarType::Str);
1387 assert_eq!(decls[1].name, "count");
1388 assert_eq!(decls[1].var_type, VarType::Int);
1389 }
1390
1391 #[test]
1392 fn decls_empty_string() {
1393 let decls = parse_decls("").unwrap();
1394 assert!(decls.is_empty());
1395 }
1396
1397 #[test]
1398 fn decls_empty_brackets() {
1399 let decls = parse_decls("[]").unwrap();
1400 assert!(decls.is_empty());
1401 }
1402
1403 #[test]
1404 fn decls_with_default_values() {
1405 let decls = parse_decls("[name = str := \"hello\", count = int := 42]").unwrap();
1406 assert_eq!(decls.len(), 2);
1407 assert_eq!(decls[0].name, "name");
1408 assert_eq!(decls[0].var_type, VarType::Str);
1409 assert_eq!(
1410 decls[0].default_value,
1411 Some(Value::Str("hello".to_string()))
1412 );
1413 assert_eq!(decls[1].name, "count");
1414 assert_eq!(decls[1].var_type, VarType::Int);
1415 assert_eq!(decls[1].default_value, Some(Value::Int(42)));
1416 }
1417
1418 #[test]
1419 fn decls_mixed_default_and_required() {
1420 let decls = parse_decls("[name = str, count = int := 10]").unwrap();
1421 assert_eq!(decls[0].default_value, None);
1422 assert_eq!(decls[1].default_value, Some(Value::Int(10)));
1423 }
1424
1425 #[test]
1426 fn decls_duplicate_name_error() {
1427 let err = parse_decls("[name = str, name = int]").unwrap_err();
1428 assert!(
1429 err.to_string().contains("duplicate parameter name"),
1430 "got: {err}"
1431 );
1432 }
1433
1434 #[test]
1435 fn decls_reserved_keyword_error() {
1436 let err = parse_decls("[list = str]").unwrap_err();
1437 assert!(err.to_string().contains("reserved keyword"), "got: {err}");
1438 }
1439
1440 #[test]
1441 fn decls_reserved_keyword_params() {
1442 let err = parse_decls("[params = str]").unwrap_err();
1443 assert!(err.to_string().contains("reserved keyword"), "got: {err}");
1444 }
1445
1446 #[test]
1447 fn enum_variant_reserved_keyword_rejected() {
1448 let err = parse_decls("[x = enum(struct, ok)]").unwrap_err();
1449 assert!(
1450 err.to_string().contains("shadows a builtin type keyword"),
1451 "got: {err}"
1452 );
1453 }
1454
1455 #[test]
1456 fn enum_variant_reserved_keyword_list_rejected() {
1457 let err = parse_decls("[x = enum(list, enum)]").unwrap_err();
1458 assert!(
1459 err.to_string().contains("shadows a builtin type keyword"),
1460 "got: {err}"
1461 );
1462 }
1463
1464 #[test]
1465 fn decls_missing_type_annotation() {
1466 let err = parse_decls("[untyped_param]").unwrap_err();
1467 assert!(
1468 err.to_string().contains("missing a type annotation"),
1469 "got: {err}"
1470 );
1471 }
1472
1473 #[test]
1474 fn decls_with_complex_types() {
1475 let decls =
1476 parse_decls("[items = list(name = str, score = float), active = bool]").unwrap();
1477 assert_eq!(decls.len(), 2);
1478 match &decls[0].var_type {
1479 VarType::List(fields) => {
1480 assert_eq!(fields.len(), 2);
1481 assert_eq!(fields[0].name, "name");
1482 assert_eq!(fields[1].name, "score");
1483 assert_eq!(fields[1].var_type, VarType::Float);
1484 }
1485 other => panic!("Expected List, got {other:?}"),
1486 }
1487 assert_eq!(decls[1].name, "active");
1488 assert_eq!(decls[1].var_type, VarType::Bool);
1489 }
1490
1491 #[test]
1492 fn decls_block_format() {
1493 let decls = parse_decls("- name = str - count = int").unwrap();
1496 assert_eq!(decls.len(), 2);
1497 assert_eq!(decls[0].name, "name");
1498 assert_eq!(decls[0].var_type, VarType::Str);
1499 assert_eq!(decls[1].name, "count");
1500 assert_eq!(decls[1].var_type, VarType::Int);
1501 }
1502
1503 #[test]
1504 fn decls_default_type_mismatch() {
1505 let err = parse_decls("[name = str := 42]").unwrap_err();
1506 assert!(
1507 err.to_string().contains("value has type"),
1508 "expected type mismatch error, got: {err}"
1509 );
1510 }
1511
1512 #[test]
1517 fn consts_requires_value() {
1518 let err = parse_consts("[MAX = int]").unwrap_err();
1519 assert!(err.to_string().contains("missing a value"), "got: {err}");
1520 }
1521
1522 #[test]
1523 fn consts_with_value() {
1524 let decls = parse_consts("[MAX = int := 100]").unwrap();
1525 assert_eq!(decls.len(), 1);
1526 assert_eq!(decls[0].name, "MAX");
1527 assert_eq!(decls[0].var_type, VarType::Int);
1528 assert_eq!(decls[0].default_value, Some(Value::Int(100)));
1529 }
1530
1531 #[test]
1532 fn consts_duplicate_name_error() {
1533 let err = parse_consts("[A = int := 1, A = int := 2]").unwrap_err();
1534 assert!(
1535 err.to_string().contains("duplicate constant name"),
1536 "got: {err}"
1537 );
1538 }
1539
1540 #[test]
1541 fn consts_reserved_keyword_error() {
1542 let err = parse_consts("[struct = str := \"hello\"]").unwrap_err();
1543 assert!(err.to_string().contains("reserved keyword"), "got: {err}");
1544 }
1545
1546 #[test]
1547 fn consts_bool_default() {
1548 let decls = parse_consts("[ENABLED = bool := true]").unwrap();
1549 assert_eq!(decls[0].default_value, Some(Value::Bool(true)));
1550 }
1551
1552 #[test]
1553 fn consts_str_default() {
1554 let decls = parse_consts("[GREETING = str := \"hi\"]").unwrap();
1555 assert_eq!(decls[0].default_value, Some(Value::Str("hi".to_string())));
1556 }
1557
1558 #[test]
1559 fn untyped_list_fails() {
1560 let err = parse_decls("[items = list()]").unwrap_err();
1561 assert!(
1562 err.to_string().contains("untyped list() is not allowed"),
1563 "got: {err}"
1564 );
1565 }
1566
1567 #[test]
1568 fn untyped_struct_fails() {
1569 let err = parse_decls("[data = struct()]").unwrap_err();
1570 assert!(
1571 err.to_string().contains("untyped struct() is not allowed"),
1572 "got: {err}"
1573 );
1574 }
1575
1576 #[test]
1577 fn unnamed_multiple_fields_list_fails() {
1578 let err = parse_decls("[items = list(str, int)]").unwrap_err();
1579 assert!(
1580 err.to_string()
1581 .contains("list with multiple fields must use named fields"),
1582 "got: {err}"
1583 );
1584 }
1585
1586 #[test]
1587 fn unquoted_string_default_fails() {
1588 let err = parse_decls("[name = str := hello]").unwrap_err();
1589 assert!(
1590 err.to_string().contains("strings must be quoted"),
1591 "got: {err}"
1592 );
1593 }
1594
1595 #[test]
1596 fn consts_type_mismatch() {
1597 let err = parse_consts("[X = int := \"not_a_number\"]").unwrap_err();
1598 assert!(
1599 err.to_string().contains("value has type"),
1600 "expected type mismatch, got: {err}"
1601 );
1602 }
1603
1604 #[test]
1609 fn enum_unit_variant_default() {
1610 let decls = parse_decls("[status = enum(Active, Paused) := Active]").unwrap();
1611 assert_eq!(
1612 decls[0].default_value,
1613 Some(Value::Str("Active".to_string()))
1614 );
1615 }
1616
1617 #[test]
1618 fn enum_unit_variant_default_on_mixed_enum() {
1619 let decls =
1621 parse_decls("[outcome = enum(Confirmed(evidence = str), Rejected) := Rejected]")
1622 .unwrap();
1623 assert_eq!(
1624 decls[0].default_value,
1625 Some(Value::Str("Rejected".to_string()))
1626 );
1627 }
1628
1629 #[test]
1630 fn enum_struct_variant_default() {
1631 let decls = parse_decls(
1633 "[outcome = enum(Confirmed(evidence = str), Rejected) := Confirmed(evidence = \"found it\")]",
1634 )
1635 .unwrap();
1636 let default = decls[0].default_value.as_ref().unwrap();
1637 match default {
1638 Value::Struct(map) => {
1639 assert_eq!(
1640 map.get("__kind__"),
1641 Some(&Value::Str("Confirmed".to_string())),
1642 "should have __kind__ tag"
1643 );
1644 assert_eq!(
1645 map.get("evidence"),
1646 Some(&Value::Str("found it".to_string())),
1647 "should have evidence field"
1648 );
1649 }
1650 other => panic!("Expected Struct for struct variant, got {other:?}"),
1651 }
1652 }
1653
1654 #[test]
1655 fn enum_struct_variant_default_multiple_fields() {
1656 let decls = parse_decls(
1657 "[r = enum(Success(msg = str, code = int), Failure) := Success(msg = \"ok\", code = 200)]",
1658 )
1659 .unwrap();
1660 let default = decls[0].default_value.as_ref().unwrap();
1661 match default {
1662 Value::Struct(map) => {
1663 assert_eq!(map.get("__kind__"), Some(&Value::Str("Success".into())));
1664 assert_eq!(map.get("msg"), Some(&Value::Str("ok".into())));
1665 assert_eq!(map.get("code"), Some(&Value::Int(200)));
1666 }
1667 other => panic!("Expected Struct, got {other:?}"),
1668 }
1669 }
1670
1671 #[test]
1672 fn enum_struct_variant_const() {
1673 let decls =
1674 parse_consts("[RESULT = enum(Success(msg = str), Failure) := Success(msg = \"done\")]")
1675 .unwrap();
1676 let default = decls[0].default_value.as_ref().unwrap();
1677 match default {
1678 Value::Struct(map) => {
1679 assert_eq!(map.get("__kind__"), Some(&Value::Str("Success".into())));
1680 assert_eq!(map.get("msg"), Some(&Value::Str("done".into())));
1681 }
1682 other => panic!("Expected Struct, got {other:?}"),
1683 }
1684 }
1685
1686 #[test]
1687 fn enum_bare_struct_variant_rejected() {
1688 let err = parse_decls("[outcome = enum(Confirmed(evidence = str), Rejected) := Confirmed]")
1690 .unwrap_err();
1691 assert!(
1692 err.to_string().contains("strings must be quoted")
1693 || err.to_string().contains("invalid default"),
1694 "bare struct variant should be rejected, got: {err}"
1695 );
1696 }
1697
1698 #[test]
1699 fn enum_unknown_variant_rejected() {
1700 let err = parse_decls("[status = enum(Active, Paused) := Nonexistent]").unwrap_err();
1701 assert!(
1702 err.to_string().contains("invalid default")
1703 || err.to_string().contains("strings must be quoted"),
1704 "unknown variant should be rejected, got: {err}"
1705 );
1706 }
1707
1708 #[test]
1709 fn enum_unit_variant_with_fields_rejected() {
1710 let err = parse_decls("[status = enum(Active, Paused) := Active(x = 1)]").unwrap_err();
1712 assert!(
1713 err.to_string().contains("invalid default"),
1714 "unit variant with fields should be rejected, got: {err}"
1715 );
1716 }
1717
1718 #[test]
1725 fn list_of_scalars_valid() {
1726 let decls = parse_decls("[items = list(str)]").unwrap();
1727 assert!(matches!(&decls[0].var_type, VarType::List(fields) if fields.len() == 1));
1728 }
1729
1730 #[test]
1731 fn list_with_named_fields_valid() {
1732 let decls = parse_decls("[items = list(name = str, score = int)]").unwrap();
1733 assert!(matches!(&decls[0].var_type, VarType::List(fields) if fields.len() == 2));
1734 }
1735
1736 #[test]
1737 fn list_of_list_valid() {
1738 let decls = parse_decls("[grid = list(list(str))]").unwrap();
1740 if let VarType::List(fields) = &decls[0].var_type {
1741 assert_eq!(fields.len(), 1);
1742 assert!(matches!(&fields[0].var_type, VarType::List(_)));
1743 } else {
1744 panic!("expected list type");
1745 }
1746 }
1747
1748 #[test]
1749 fn list_of_enum_valid() {
1750 let decls = parse_decls("[statuses = list(enum(Active, Paused))]").unwrap();
1752 if let VarType::List(fields) = &decls[0].var_type {
1753 assert_eq!(fields.len(), 1);
1754 assert!(matches!(&fields[0].var_type, VarType::Enum(_)));
1755 } else {
1756 panic!("expected list type");
1757 }
1758 }
1759
1760 #[test]
1761 fn struct_with_list_field_valid() {
1762 let decls = parse_decls("[cfg = struct(tags = list(str), name = str)]").unwrap();
1763 if let VarType::Struct(fields) = &decls[0].var_type {
1764 assert_eq!(fields.len(), 2);
1765 assert!(matches!(&fields[0].var_type, VarType::List(_)));
1766 } else {
1767 panic!("expected struct type");
1768 }
1769 }
1770
1771 #[test]
1772 fn struct_with_enum_field_valid() {
1773 let decls = parse_decls("[cfg = struct(status = enum(On, Off), name = str)]").unwrap();
1774 if let VarType::Struct(fields) = &decls[0].var_type {
1775 assert_eq!(fields.len(), 2);
1776 assert!(matches!(&fields[0].var_type, VarType::Enum(_)));
1777 } else {
1778 panic!("expected struct type");
1779 }
1780 }
1781
1782 #[test]
1783 fn struct_with_nested_struct_field_valid() {
1784 let decls =
1785 parse_decls("[cfg = struct(inner = struct(x = int, y = int), name = str)]").unwrap();
1786 if let VarType::Struct(fields) = &decls[0].var_type {
1787 assert_eq!(fields.len(), 2);
1788 assert!(matches!(&fields[0].var_type, VarType::Struct(_)));
1789 } else {
1790 panic!("expected struct type");
1791 }
1792 }
1793
1794 #[test]
1795 fn list_of_list_of_int_valid() {
1796 let decls = parse_decls("[matrix = list(list(int))]").unwrap();
1798 if let VarType::List(outer) = &decls[0].var_type {
1799 if let VarType::List(inner) = &outer[0].var_type {
1800 assert_eq!(inner.len(), 1);
1801 assert!(matches!(&inner[0].var_type, VarType::Int));
1802 } else {
1803 panic!("expected inner list");
1804 }
1805 } else {
1806 panic!("expected outer list");
1807 }
1808 }
1809
1810 #[test]
1813 fn list_of_raw_struct_rejected_as_redundant() {
1814 let err = parse_decls("[items = list(struct(name = str, score = int))]").unwrap_err();
1815 assert!(err.to_string().contains("redundant"), "got: {err}");
1816 }
1817
1818 #[test]
1819 fn list_of_strong_struct_alias_unwraps_cleanly() {
1820 let mut aliases = HashMap::new();
1821 aliases.insert(
1822 "MyItem".to_string(),
1823 VarType::Struct(vec![
1824 VarDecl {
1825 name: "name".to_string(),
1826 var_type: VarType::Str,
1827 default_value: None,
1828 },
1829 VarDecl {
1830 name: "score".to_string(),
1831 var_type: VarType::Int,
1832 default_value: None,
1833 },
1834 ]),
1835 );
1836 let var_type = parse_type_annotation("list(MyItem)", &aliases, &HashMap::new()).unwrap();
1837 if let VarType::List(ref fields) = var_type {
1838 assert_eq!(fields.len(), 2);
1839 assert_eq!(fields[0].name, "name");
1840 assert_eq!(fields[1].name, "score");
1841 } else {
1842 panic!("expected VarType::List");
1843 }
1844 }
1845
1846 #[test]
1847 fn list_of_named_struct_field_allowed() {
1848 let decls = parse_decls("[items = list(item = struct(name = str, score = int))]").unwrap();
1849 if let VarType::List(ref fields) = decls[0].var_type {
1850 assert_eq!(fields.len(), 1);
1851 assert_eq!(fields[0].name, "item");
1852 if let VarType::Struct(ref inner) = fields[0].var_type {
1853 assert_eq!(inner.len(), 2);
1854 assert_eq!(inner[0].name, "name");
1855 assert_eq!(inner[1].name, "score");
1856 } else {
1857 panic!("expected inner VarType::Struct");
1858 }
1859 } else {
1860 panic!("expected VarType::List");
1861 }
1862 }
1863
1864 #[test]
1869 fn type_option_str() {
1870 let result = parse_type("option(str)").unwrap();
1871 match result {
1872 VarType::Option(inner) => {
1873 assert_eq!(*inner, VarType::Str);
1874 }
1875 other => panic!("Expected Option, got {other:?}"),
1876 }
1877 }
1878
1879 #[test]
1880 fn type_option_int() {
1881 let result = parse_type("option(int)").unwrap();
1882 assert!(result.is_option());
1883 assert_eq!(*result.option_inner_type().unwrap(), VarType::Int);
1884 }
1885
1886 #[test]
1887 fn type_option_with_spaces() {
1888 let result = parse_type("option( str )").unwrap();
1889 assert!(result.is_option());
1890 assert_eq!(*result.option_inner_type().unwrap(), VarType::Str);
1891 }
1892
1893 #[test]
1894 fn type_option_nested_list() {
1895 let result = parse_type("option(list(name = str))").unwrap();
1896 assert!(result.is_option());
1897 assert!(matches!(
1898 result.option_inner_type().unwrap(),
1899 VarType::List(_)
1900 ));
1901 }
1902
1903 #[test]
1904 fn type_option_nested_struct() {
1905 let result = parse_type("option(struct(x = int, y = int))").unwrap();
1906 assert!(result.is_option());
1907 assert!(matches!(
1908 result.option_inner_type().unwrap(),
1909 VarType::Struct(_)
1910 ));
1911 }
1912
1913 #[test]
1914 fn type_option_nested_option() {
1915 let result = parse_type("option(option(str))").unwrap();
1916 assert!(result.is_option());
1917 let inner = result.option_inner_type().unwrap();
1918 assert!(inner.is_option());
1919 assert_eq!(*inner.option_inner_type().unwrap(), VarType::Str);
1920 }
1921
1922 #[test]
1923 fn type_option_display() {
1924 let result = parse_type("option(str)").unwrap();
1925 assert_eq!(format!("{result}"), "option(str)");
1926 }
1927
1928 #[test]
1929 fn type_option_display_nested() {
1930 let result = parse_type("option(option(int))").unwrap();
1931 assert_eq!(format!("{result}"), "option(option(int))");
1932 }
1933
1934 #[test]
1935 fn type_option_empty_rejected() {
1936 assert!(parse_type("option()").is_err());
1937 }
1938
1939 #[test]
1940 fn type_option_malformed_rejected() {
1941 assert!(parse_type("option").is_err());
1942 assert!(parse_type("option(").is_err());
1943 }
1944
1945 #[test]
1946 fn option_default_none() {
1947 let decls = parse_decls("[x = option(str) := None]").unwrap();
1948 assert_eq!(decls[0].default_value, Some(Value::None));
1949 }
1950
1951 #[test]
1952 fn option_default_some() {
1953 let decls = parse_decls("[x = option(str) := \"hello\"]").unwrap();
1955 assert_eq!(decls[0].default_value, Some(Value::Str("hello".into())));
1956 }
1957
1958 #[test]
1959 fn option_reserved_name() {
1960 let result = parse_decls("[option = str]");
1961 assert!(result.is_err());
1962 }
1963
1964 #[test]
1965 fn list_of_option() {
1966 let result = parse_type("list(option(str))").unwrap();
1967 match result {
1968 VarType::List(fields) => {
1969 assert_eq!(fields.len(), 1);
1970 assert!(fields[0].var_type.is_option());
1971 }
1972 other => panic!("Expected List, got {other:?}"),
1973 }
1974 }
1975
1976 #[test]
1977 fn prohibited_angle_and_square_brackets() {
1978 assert!(parse_type("list<str>").is_err());
1979 assert!(parse_type("list[str]").is_err());
1980 assert!(parse_type("struct<x = int>").is_err());
1981 assert!(parse_type("struct[x = int]").is_err());
1982 assert!(parse_type("enum<A, B>").is_err());
1983 assert!(parse_type("enum[A, B]").is_err());
1984 assert!(parse_type("tmpl<x = int>").is_err());
1985 assert!(parse_type("tmpl[x = int]").is_err());
1986 assert!(parse_type("option<str>").is_err());
1987 assert!(parse_type("option[str]").is_err());
1988 }
1989}