Skip to main content

md_tmpl/frontmatter/
params.rs

1//! Parameter declaration parsing for frontmatter `params:` blocks.
2//!
3//! Handles both inline (`[name = str, count = int]`) and block
4//! (`- name = str`) formats, including default values and nested types.
5
6use 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
21/// Join YAML continuation lines: any line starting with whitespace is appended
22/// to the preceding logical line.
23pub(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            // Continuation of previous logical line.
28            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
41/// Parse the value part after `params:` or `consts:`.
42///
43/// Supports both inline and block list formats:
44/// - Inline: `[name = str, count = int]`
45pub(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        // `params:` with no value and no continuation lines → empty params.
55        return Ok(vec![]);
56    }
57
58    // Strip only the outermost `[` and `]` (inline YAML flow sequence).
59    let inner = rest
60        .strip_prefix('[')
61        .and_then(|s| s.strip_suffix(']'))
62        .unwrap_or(rest);
63
64    // Handle block list format: entries are `- name = type` joined by spaces
65    // (after continuation line joining, the `- ` markers are preserved).
66    let entries = if inner.contains("- ") {
67        // Split on ` - ` to separate entries, then strip leading `- ` from
68        // the first entry if present.
69        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        // Inline format: split on commas at bracket-depth 0.
79        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
106/// Parse a single declaration entry (e.g. `name = str := "default"`) into a [`VarDecl`].
107fn 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    // Find `=` at depth 0 to split name from type+default.
120    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    // Check duplicate names.
131    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    // Check reserved keywords.
141    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    // Find `:=` at depth 0 to split type from default value.
149    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    // For constants, the default value is mandatory.
177    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    // Validate that the default value matches the declared type.
184    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
201// Compatibility wrapper for `params:` removed as it is now unused.
202
203/// Strip enclosing compound type delimiter pair `(...)`.
204pub(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
215/// Split a string on commas at bracket-depth 0.
216pub(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
241/// Find the first occurrence of `target` at bracket-depth 0.
242pub(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
261/// Find the position of `:=` at bracket-depth zero.
262fn 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
282/// Parse a type annotation string into a [`VarType`].
283///
284/// Supported forms:
285/// - `str` → [`VarType::Str`]
286/// - `bool` → [`VarType::Bool`]
287/// - `int` → [`VarType::Int`]
288/// - `float` → [`VarType::Float`]
289/// - `list(name = str, count = int)` → [`VarType::List`] with field declarations
290/// - `struct(key = str)` → [`VarType::Struct`] with field declarations
291/// - `enum(A, B(field = type))` → [`VarType::Enum`] with variant declarations
292///
293/// # Errors
294///
295/// Returns an error string if the type annotation is malformed or
296/// references an unknown type name.
297fn 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
306/// Parses a type annotation string into a `VarType`.
307///
308/// # Errors
309/// Returns an error string if the type annotation syntax is invalid or references an unknown type alias.
310pub 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    // Check type aliases first (own or inherited).
336    if let Some(ty) = type_aliases.get(s) {
337        return Ok(ty.clone());
338    }
339
340    // Check dotted import paths: `stem.TypeName`.
341    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
378/// Parse an enum type like `enum(Confirmed(evidence = list(text = str)), Inconclusive)`.
379fn 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    // Reject variant names that shadow builtin type keywords.
422    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
433/// Parse a compound type like `list(name = str, count = int)`.
434fn 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    // Reject literal raw struct declarations inside list definitions (e.g. list(struct(name = str, count = int))).
456    // Users should write named fields directly (e.g. list(name = str, count = int)) or reference a strong Type alias.
457    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 the inner type resolved to a strong struct alias (e.g. list(MyStruct)),
469    // unwrap the struct fields directly into the list fields.
470    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
478/// Parse a compound type like `struct(key = str, value = int)`.
479fn 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
505/// Parse a tmpl type like `tmpl(name = str, count = int)`.
506fn 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
524/// Parse `option(T)` into [`VarType::Option`].
525fn 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
544/// Parse field declarations like `name = str, count = int` into [`VarDecl`]s.
545fn 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
572/// Parse the *inner* content of a `{key = value, ...}` struct default into
573/// a [`Value::Struct`].
574///
575/// Uses `=` as the key-value separator (not `:`) and curly braces for
576/// delimiters.
577fn 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
604/// Resolve a const name used as a default value.
605///
606/// Looks up `name` in the available constants map, supporting both local
607/// const names (e.g. `MAX`) and imported const names (e.g. `lib.LIMIT`).
608/// Returns a clone of the const value if found.
609fn 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
617/// Parse a default value string into a [`Value`].
618///
619/// Supports:
620/// - Inline lists: `[1, 2, 3]` or `['a', 'b']`
621/// - Inline structs: `{key = value, key2 = value2}`
622/// - List of structs: `[{k = v1}, {k = v2}]`
623/// - Quoted strings: `"hello"` or `'hello'`
624/// - Integers, floats, booleans
625///
626/// Lists use `[]` and structs use `{}` with `=` as the key-value separator.
627pub(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    // Handle list defaults: [a, b, c]
638    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    // Handle struct defaults: {key = value, ...}
664    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    // Quoted string
681    if let Some(inner) = crate::consts::strip_string_literal(s) {
682        return Some(Value::Str(inner.to_string()));
683    }
684
685    // Boolean
686    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    // Integer
694    if let Ok(n) = s.parse::<i64>() {
695        return Some(Value::Int(n));
696    }
697
698    // Float
699    if let Ok(n) = s.parse::<f64>() {
700        return Some(Value::Float(n));
701    }
702
703    // Handle option(T) defaults: None maps to Value::None, otherwise delegate
704    // to the inner type.
705    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 the expected type is an Enum, handle variant identifiers.
713    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    // Intentional removal of fallback: unquoted strings are no longer allowed
722    // as default values. All string defaults must be explicitly quoted.
723    None
724}
725
726/// Parse a default value for an enum variant — either a unit variant name
727/// (e.g. `Active`) or a struct variant with fields (e.g. `Error(msg = "oops")`).
728fn parse_enum_default_value(
729    s: &str,
730    variants: &[crate::types::VariantDecl],
731    available_consts: &HashMap<String, Value>,
732) -> Option<Value> {
733    // Check for struct variant default: VariantName(field = value, ...)
734    // Uses () to match the type declaration syntax and avoid ambiguity
735    // with <> which is used for struct/list defaults.
736    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            // Find the variant declaration.
741            let variant = variants.iter().find(|v| v.name == variant_name);
742            match variant {
743                Some(v) if v.fields.is_empty() => {
744                    return None; // Unit variant can't have fields
745                }
746                Some(v) => {
747                    // Parse field values and build a tagged dict.
748                    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, // Unknown variant
777            }
778        }
779    }
780
781    // Bare identifier — must be a known unit variant.
782    let variant = variants.iter().find(|v| v.name == s);
783    match variant {
784        Some(v) if !v.fields.is_empty() => {
785            // Struct variant without fields — reject.
786            None
787        }
788        Some(_) => Some(Value::Str(s.to_string())),
789        None => None, // Unknown variant name
790    }
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    /// Helper: parse a type annotation with empty aliases/imports.
808    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    /// Helper: parse declarations (params, not constants) with empty aliases/imports.
815    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    /// Helper: parse constant declarations with empty aliases/imports.
823    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    // =========================================================================
831    // join_continuation_lines
832    // =========================================================================
833
834    #[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        // If the very first line is indented, there's no previous line to join to,
860        // so it becomes its own logical line.
861        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    // =========================================================================
891    // split_at_depth_zero
892    // =========================================================================
893
894    #[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    // =========================================================================
945    // find_char_at_depth_zero
946    // =========================================================================
947
948    #[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    // =========================================================================
990    // find_assign_default_at_depth_zero (internal, tested via parse_declarations)
991    // =========================================================================
992
993    #[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        // A bare `:` without `=` should not match.
1014        let result = find_assign_default_at_depth_zero("a: b");
1015        assert_eq!(result, None);
1016    }
1017
1018    // =========================================================================
1019    // parse_default_value
1020    // =========================================================================
1021
1022    #[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        // Unquoted non-numeric/non-bool strings are no longer allowed.
1118        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    // =========================================================================
1191    // parse_type_annotation
1192    // =========================================================================
1193
1194    #[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    // =========================================================================
1378    // parse_declarations (params mode)
1379    // =========================================================================
1380
1381    #[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        // After continuation joining, block entries look like:
1494        // "- name = str - count = int"
1495        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    // =========================================================================
1513    // parse_declarations (constants mode)
1514    // =========================================================================
1515
1516    #[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    // =========================================================================
1605    // Enum defaults and consts
1606    // =========================================================================
1607
1608    #[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        // Unit variant default on an enum that also has struct variants.
1620        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        // Struct variant default with inline field values.
1632        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        // Struct variant without fields → must be rejected.
1689        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        // Trying to give fields to a unit variant.
1711        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    // =========================================================================
1719    // Type nesting rules
1720    // =========================================================================
1721
1722    // --- Positive: valid nesting ---
1723
1724    #[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        // Nested lists (e.g. matrix, grid, coordinates).
1739        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        // List where each element is an enum value.
1751        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        // Matrix of ints — deeply nested.
1797        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    // --- Negative: forbidden nesting ---
1811
1812    #[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    // =========================================================================
1865    // option(T) type parsing
1866    // =========================================================================
1867
1868    #[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        // Transparent option: := "hello" stores the raw string, not a Some(val=...) struct.
1954        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}