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> {
33 let mut logical: Vec<String> = Vec::new();
34 for raw in block.lines() {
35 let trimmed = raw.trim();
36 if trimmed.is_empty() || trimmed.starts_with(crate::consts::FM_COMMENT_PREFIX) {
38 continue;
39 }
40 if raw.starts_with(' ') || raw.starts_with('\t') {
41 if let Some(prev) = logical.last_mut() {
43 prev.push(' ');
44 prev.push_str(trimmed);
45 } else {
46 logical.push(raw.to_string());
47 }
48 } else {
49 logical.push(raw.to_string());
50 }
51 }
52 logical
53}
54
55pub(crate) fn parse_declarations(
60 rest: &str,
61 type_aliases: &HashMap<String, VarType>,
62 resolved_imports: &HashMap<String, ImportedNamespace>,
63 is_constant: bool,
64 available_consts: &HashMap<String, Value>,
65) -> Result<Vec<VarDecl>, TemplateError> {
66 let rest = rest.trim();
67 if rest.is_empty() {
68 return Ok(vec![]);
70 }
71
72 let inner = rest
74 .strip_prefix(crate::consts::BRACKET_OPEN)
75 .and_then(|s| s.strip_suffix(crate::consts::BRACKET_CLOSE))
76 .unwrap_or(rest);
77
78 let entries = if inner.contains("- ") {
81 let mut result = Vec::new();
84 for part in inner.split(" - ") {
85 let part = part.trim().strip_prefix('-').unwrap_or(part).trim();
86 if !part.is_empty() {
87 result.push(part.to_string());
88 }
89 }
90 result
91 } else {
92 split_at_depth_zero(inner)
94 .into_iter()
95 .map(ToString::to_string)
96 .collect()
97 };
98
99 let mut decls = Vec::new();
100 let mut seen_names = crate::compat::HashSet::new();
101 let mut current_consts = available_consts.clone();
102 for entry in &entries {
103 let e = entry.trim();
104 let trimmed = crate::consts::strip_string_literal(e).unwrap_or(e).trim();
105 if let Some(decl) = parse_single_declaration(
106 trimmed,
107 type_aliases,
108 resolved_imports,
109 is_constant,
110 &mut current_consts,
111 &mut seen_names,
112 )? {
113 decls.push(decl);
114 }
115 }
116
117 Ok(decls)
118}
119
120fn parse_single_declaration(
122 trimmed: &str,
123 type_aliases: &HashMap<String, VarType>,
124 resolved_imports: &HashMap<String, ImportedNamespace>,
125 is_constant: bool,
126 current_consts: &mut HashMap<String, Value>,
127 seen_names: &mut crate::compat::HashSet<String>,
128) -> Result<Option<VarDecl>, TemplateError> {
129 if trimmed.is_empty() {
130 return Ok(None);
131 }
132
133 let Some(eq_pos) = find_char_at_depth_zero(trimmed, crate::consts::EQUALS) else {
135 let label = if is_constant { "constant" } else { "param" };
136 return Err(TemplateError::syntax(format!(
137 "{label} '{trimmed}' is missing a type annotation (expected 'name = type')"
138 )));
139 };
140
141 let name = trimmed[..eq_pos].trim().to_string();
142 let type_and_default = trimmed[eq_pos + 1..].trim();
143
144 if !seen_names.insert(name.clone()) {
146 let err = if is_constant {
147 crate::consts::ERR_DUPLICATE_CONST
148 } else {
149 crate::consts::ERR_DUPLICATE_PARAM
150 };
151 return Err(TemplateError::syntax(format!("{err}: '{name}'")));
152 }
153
154 if crate::consts::RESERVED_NAMES.contains(&name.as_str()) {
156 return Err(TemplateError::syntax(format!(
157 "{}: '{name}'",
158 crate::consts::ERR_RESERVED_KEYWORD
159 )));
160 }
161
162 let (type_str, default_part) =
164 if let Some(assign_pos) = find_assign_default_at_depth_zero(type_and_default) {
165 (
166 type_and_default[..assign_pos].trim(),
167 Some(type_and_default[assign_pos + 2..].trim()),
168 )
169 } else {
170 (type_and_default, None)
171 };
172
173 let var_type = parse_type_annotation(type_str, type_aliases, resolved_imports)
174 .map_err(|e| TemplateError::syntax(format!("declaration '{name}': {e}")))?;
175
176 let default_value = if let Some(dp) = default_part {
177 let default = parse_default_value_full(
178 dp,
179 &var_type,
180 current_consts,
181 type_aliases,
182 resolved_imports,
183 )
184 .or_else(|| resolve_const_default(dp, current_consts))
185 .or_else(|| resolve_kinds_default(dp, type_aliases, resolved_imports))
186 .ok_or_else(|| {
187 TemplateError::syntax(format!(
188 "invalid default value '{dp}' for declaration '{name}' (strings must be quoted)"
189 ))
190 })?;
191 current_consts.insert(name.clone(), default.clone());
192 Some(default)
193 } else {
194 None
195 };
196
197 if is_constant && default_value.is_none() {
199 return Err(TemplateError::syntax(format!(
200 "constant '{name}' is missing a value (expected 'name = type := value')"
201 )));
202 }
203
204 if let Some(ref default) = default_value
206 && !var_type.matches(default)
207 {
208 let label = if is_constant { "constant" } else { "param" };
209 return Err(TemplateError::syntax(format!(
210 "{label} '{name}': value has type '{}' but declared type is '{var_type}'",
211 default.type_name()
212 )));
213 }
214
215 Ok(Some(VarDecl {
216 name,
217 var_type,
218 default_value,
219 }))
220}
221
222pub(crate) fn strip_type_brackets(s: &str) -> Option<&str> {
226 if let (Some(inner), true) = (
227 s.strip_prefix(crate::consts::PAREN_OPEN),
228 s.ends_with(crate::consts::PAREN_CLOSE),
229 ) {
230 Some(&inner[..inner.len() - 1])
231 } else {
232 None
233 }
234}
235
236pub(crate) fn split_at_depth_zero(input: &str) -> Vec<&str> {
245 use crate::consts::{
246 ANGLE_CLOSE, ANGLE_OPEN, BRACE_CLOSE, BRACE_OPEN, BRACKET_CLOSE, BRACKET_OPEN, COMMA,
247 PAREN_CLOSE, PAREN_OPEN, QUOTE_DOUBLE, QUOTE_SINGLE,
248 };
249 let mut entries = Vec::new();
250 let mut depth: u32 = 0;
251 let mut start = 0;
252 let mut in_quote: Option<char> = None;
255 for (i, ch) in input.char_indices() {
256 if let Some(q) = in_quote {
257 if ch == q {
258 in_quote = None;
259 }
260 continue;
261 }
262 match ch {
263 QUOTE_DOUBLE | QUOTE_SINGLE => in_quote = Some(ch),
264 ANGLE_OPEN | BRACKET_OPEN | PAREN_OPEN | BRACE_OPEN => depth += 1,
265 ANGLE_CLOSE | BRACKET_CLOSE | PAREN_CLOSE | BRACE_CLOSE => {
266 depth = depth.saturating_sub(1);
267 }
268 COMMA if depth == 0 => {
269 entries.push(&input[start..i]);
270 start = i + 1;
271 }
272 _ => {}
273 }
274 }
275 entries.push(&input[start..]);
276 entries
277}
278
279pub(crate) fn find_char_at_depth_zero(input: &str, target: char) -> Option<usize> {
281 use crate::consts::{
282 ANGLE_CLOSE, ANGLE_OPEN, BRACE_CLOSE, BRACE_OPEN, BRACKET_CLOSE, BRACKET_OPEN, PAREN_CLOSE,
283 PAREN_OPEN,
284 };
285 let mut depth: u32 = 0;
286 for (i, ch) in input.char_indices() {
287 match ch {
288 ANGLE_OPEN | BRACKET_OPEN | PAREN_OPEN | BRACE_OPEN => depth += 1,
289 ANGLE_CLOSE | BRACKET_CLOSE | PAREN_CLOSE | BRACE_CLOSE => {
290 depth = depth.saturating_sub(1);
291 }
292 c if c == target && depth == 0 => return Some(i),
293 _ => {}
294 }
295 }
296 None
297}
298
299fn find_assign_default_at_depth_zero(input: &str) -> Option<usize> {
301 use crate::consts::{
302 ANGLE_CLOSE_BYTE, ANGLE_OPEN_BYTE, BRACE_CLOSE_BYTE, BRACE_OPEN_BYTE, BRACKET_CLOSE_BYTE,
303 BRACKET_OPEN_BYTE, COLON_BYTE, EQUALS_BYTE, PAREN_CLOSE_BYTE, PAREN_OPEN_BYTE,
304 };
305 let mut depth: u32 = 0;
306 let bytes = input.as_bytes();
307 for (i, &b) in bytes.iter().enumerate() {
308 match b {
309 ANGLE_OPEN_BYTE | BRACKET_OPEN_BYTE | PAREN_OPEN_BYTE | BRACE_OPEN_BYTE => depth += 1,
310 ANGLE_CLOSE_BYTE | BRACKET_CLOSE_BYTE | PAREN_CLOSE_BYTE | BRACE_CLOSE_BYTE => {
311 depth = depth.saturating_sub(1);
312 }
313 COLON_BYTE if depth == 0 && bytes.get(i + 1) == Some(&EQUALS_BYTE) => return Some(i),
314 _ => {}
315 }
316 }
317 None
318}
319
320fn starts_with_compound_type(s: &str, keyword: &str) -> bool {
336 if let Some(rest) = s.strip_prefix(keyword) {
337 let rest = rest.trim_start();
338 rest.starts_with(crate::consts::PAREN_OPEN)
339 } else {
340 false
341 }
342}
343
344pub fn parse_type_annotation(
349 s: &str,
350 type_aliases: &HashMap<String, VarType>,
351 resolved_imports: &HashMap<String, ImportedNamespace>,
352) -> Result<VarType, String> {
353 use crate::consts::{
354 ANGLE_OPEN, BRACKET_OPEN, ERR_COMPOUND_BRACKETS_PROHIBITED, TYPE_BOOL, TYPE_ENUM,
355 TYPE_FLOAT, TYPE_INT, TYPE_LIST, TYPE_OPTION, TYPE_STR, TYPE_STRUCT, TYPE_TMPL,
356 };
357
358 let s = crate::consts::strip_string_literal(s.trim())
359 .unwrap_or(s.trim())
360 .trim();
361
362 for kw in &[TYPE_LIST, TYPE_STRUCT, TYPE_ENUM, TYPE_TMPL, TYPE_OPTION] {
363 if let Some(rest) = s.strip_prefix(kw) {
364 let rest_trimmed = rest.trim_start();
365 if rest_trimmed.starts_with(ANGLE_OPEN) || rest_trimmed.starts_with(BRACKET_OPEN) {
366 return Err(format!(
367 "compound type '{kw}': {ERR_COMPOUND_BRACKETS_PROHIBITED}"
368 ));
369 }
370 }
371 }
372
373 if let Some(ty) = type_aliases.get(s) {
375 return Ok(ty.clone());
376 }
377
378 if let Some(dot_pos) = s.find(crate::consts::PATH_SEP) {
380 let stem = &s[..dot_pos];
381 let type_name = &s[dot_pos + 1..];
382 if let Some(ns) = resolved_imports.get(stem) {
383 if let Some(ty) = ns.type_aliases.get(type_name) {
384 return Ok(ty.clone());
385 }
386 if let Some(ty) = ns.param_types.get(type_name) {
387 return Ok(ty.clone());
388 }
389 return Err(format!("import '{stem}' has no type '{type_name}'"));
390 }
391 }
392
393 if s == TYPE_STR {
394 Ok(VarType::Str)
395 } else if s == TYPE_BOOL {
396 Ok(VarType::Bool)
397 } else if s == TYPE_INT {
398 Ok(VarType::Int)
399 } else if s == TYPE_FLOAT {
400 Ok(VarType::Float)
401 } else if starts_with_compound_type(s, TYPE_LIST) {
402 parse_compound_type_list(s, type_aliases, resolved_imports)
403 } else if starts_with_compound_type(s, TYPE_STRUCT) {
404 parse_compound_type_struct(s, type_aliases, resolved_imports)
405 } else if starts_with_compound_type(s, TYPE_ENUM) {
406 parse_enum_type(s, type_aliases, resolved_imports)
407 } else if starts_with_compound_type(s, TYPE_TMPL) {
408 parse_tmpl_type(s, type_aliases, resolved_imports)
409 } else if starts_with_compound_type(s, TYPE_OPTION) {
410 parse_option_type(s, type_aliases, resolved_imports)
411 } else {
412 Err(format!("unknown type '{s}'"))
413 }
414}
415
416fn parse_enum_type(
418 s: &str,
419 type_aliases: &HashMap<String, VarType>,
420 resolved_imports: &HashMap<String, ImportedNamespace>,
421) -> Result<VarType, String> {
422 use crate::{consts::TYPE_ENUM, types::VariantDecl};
423
424 let rest = s.strip_prefix(TYPE_ENUM).unwrap_or("").trim();
425 let Some(inner) = strip_type_brackets(rest) else {
426 return Err(format!("malformed enum type: '{s}'"));
427 };
428 let entries = split_at_depth_zero(inner);
429 let mut variants = Vec::new();
430 for entry in entries {
431 let entry = entry.trim();
432 if entry.is_empty() {
433 continue;
434 }
435 if let (Some(open_idx), Some(close_idx)) = (
436 entry.find(crate::consts::PAREN_OPEN),
437 entry.rfind(crate::consts::PAREN_CLOSE),
438 ) {
439 let name = entry[..open_idx].trim().to_string();
440 let fields_str = &entry[open_idx + 1..close_idx];
441 let fields = parse_field_declarations(fields_str, type_aliases, resolved_imports)?;
442 if fields.iter().any(|f| f.name.is_empty()) {
443 return Err(
444 "enum struct variant must use named fields (e.g. Variant(name = str))"
445 .to_string(),
446 );
447 }
448 variants.push(VariantDecl { name, fields });
449 continue;
450 }
451 variants.push(VariantDecl {
452 name: entry.to_string(),
453 fields: vec![],
454 });
455 }
456 if variants.is_empty() {
457 return Err("enum must have at least one variant".to_string());
458 }
459 for v in &variants {
461 if crate::consts::RESERVED_NAMES.contains(&v.name.as_str()) {
462 return Err(format!(
463 "enum variant name '{}' shadows a builtin type keyword",
464 v.name
465 ));
466 }
467 }
468 Ok(VarType::Enum(variants))
469}
470
471fn parse_compound_type_list(
473 s: &str,
474 type_aliases: &HashMap<String, VarType>,
475 resolved_imports: &HashMap<String, ImportedNamespace>,
476) -> Result<VarType, String> {
477 use crate::consts::TYPE_LIST;
478
479 let rest = s.strip_prefix(TYPE_LIST).unwrap_or("").trim();
480 let Some(inner) = strip_type_brackets(rest) else {
481 return Err(format!("malformed list type: '{s}'"));
482 };
483 let fields = parse_field_declarations(inner, type_aliases, resolved_imports)?;
484 if fields.is_empty() {
485 return Err("untyped list() is not allowed; must specify element type or fields (e.g., list(str) or list(name = str))".to_string());
486 }
487 if fields.len() > 1 && fields.iter().any(|f| f.name.is_empty()) {
488 return Err(
489 "list with multiple fields must use named fields (e.g. list(name = str, count = int))"
490 .to_string(),
491 );
492 }
493 let inner_trimmed = inner.trim();
496 if inner_trimmed.starts_with(crate::consts::TYPE_STRUCT_ANGLE_PREFIX)
497 || inner_trimmed.starts_with(crate::consts::TYPE_STRUCT_PREFIX)
498 || inner_trimmed.starts_with(crate::consts::TYPE_STRUCT_BRACKET_PREFIX)
499 || inner_trimmed.starts_with(crate::consts::TYPE_STRUCT_SPACE_PREFIX)
500 {
501 return Err(
502 "list(struct(..)) is redundant; use named fields directly: list(name = str, count = int)"
503 .to_string(),
504 );
505 }
506 if fields.len() == 1 && fields[0].name.is_empty() {
509 if let VarType::Struct(ref struct_fields) = fields[0].var_type {
510 return Ok(VarType::List(struct_fields.clone()));
511 }
512 }
513 Ok(VarType::List(fields))
514}
515
516fn parse_compound_type_struct(
518 s: &str,
519 type_aliases: &HashMap<String, VarType>,
520 resolved_imports: &HashMap<String, ImportedNamespace>,
521) -> Result<VarType, String> {
522 use crate::consts::TYPE_STRUCT;
523
524 let rest = s.strip_prefix(TYPE_STRUCT).unwrap_or("").trim();
525 let Some(inner) = strip_type_brackets(rest) else {
526 return Err(format!("malformed struct type: '{s}'"));
527 };
528 let fields = parse_field_declarations(inner, type_aliases, resolved_imports)?;
529 if fields.is_empty() {
530 return Err(
531 "untyped struct() is not allowed; must specify fields (e.g., struct(name = str))"
532 .to_string(),
533 );
534 }
535 if fields.iter().any(|f| f.name.is_empty()) {
536 return Err(
537 "struct must use named fields (e.g. struct(name = str, count = int))".to_string(),
538 );
539 }
540 Ok(VarType::Struct(fields))
541}
542
543fn parse_tmpl_type(
545 s: &str,
546 type_aliases: &HashMap<String, VarType>,
547 resolved_imports: &HashMap<String, ImportedNamespace>,
548) -> Result<VarType, String> {
549 use crate::consts::TYPE_TMPL;
550
551 let rest = s.strip_prefix(TYPE_TMPL).unwrap_or("").trim();
552 let Some(inner) = strip_type_brackets(rest) else {
553 return Err(format!("malformed tmpl type: '{s}'"));
554 };
555 let fields = parse_field_declarations(inner, type_aliases, resolved_imports)?;
556 if fields.iter().any(|f| f.name.is_empty()) {
557 return Err("tmpl must use named fields (e.g. tmpl(name = str, count = int))".to_string());
558 }
559 Ok(VarType::Tmpl(fields))
560}
561
562fn parse_option_type(
564 s: &str,
565 type_aliases: &HashMap<String, VarType>,
566 resolved_imports: &HashMap<String, ImportedNamespace>,
567) -> Result<VarType, String> {
568 use crate::consts::TYPE_OPTION;
569
570 let rest = s.strip_prefix(TYPE_OPTION).unwrap_or("").trim();
571 let Some(inner) = strip_type_brackets(rest) else {
572 return Err(format!("malformed option type: '{s}'"));
573 };
574 let inner = inner.trim();
575 if inner.is_empty() {
576 return Err("option() requires an inner type (e.g. option(str))".to_string());
577 }
578 let inner_type = parse_type_annotation(inner, type_aliases, resolved_imports)?;
579 Ok(VarType::Option(Box::new(inner_type)))
580}
581
582fn parse_field_declarations(
584 inner: &str,
585 type_aliases: &HashMap<String, VarType>,
586 resolved_imports: &HashMap<String, ImportedNamespace>,
587) -> Result<Vec<VarDecl>, String> {
588 let entries = split_at_depth_zero(inner);
589 let mut decls = Vec::new();
590 for f in &entries {
591 let f = f.trim();
592 if f.is_empty() {
593 continue;
594 }
595 let (name, type_str) =
596 if let Some(eq_pos) = find_char_at_depth_zero(f, crate::consts::EQUALS) {
597 (f[..eq_pos].trim().to_string(), f[eq_pos + 1..].trim())
598 } else {
599 (String::new(), f)
600 };
601 let var_type = parse_type_annotation(type_str, type_aliases, resolved_imports)?;
602 decls.push(VarDecl {
603 name,
604 var_type,
605 default_value: None,
606 });
607 }
608 Ok(decls)
609}
610
611fn parse_struct_default(
617 inner: &str,
618 fields: &[VarDecl],
619 available_consts: &HashMap<String, Value>,
620 type_aliases: &HashMap<String, VarType>,
621 resolved_imports: &HashMap<String, ImportedNamespace>,
622) -> Value {
623 let entries = split_at_depth_zero(inner);
624 let mut map = HashMap::new();
625 for e in entries {
626 let e = e.trim();
627 if e.is_empty() {
628 continue;
629 }
630 if let Some(eq_pos) = find_char_at_depth_zero(e, crate::consts::EQUALS) {
631 let key = e[..eq_pos].trim();
632 let val_str = e[eq_pos + 1..].trim();
633 let field_type = fields
634 .iter()
635 .find(|d| d.name == key)
636 .map_or(&VarType::Str, |d| &d.var_type);
637 if let Some(v) = parse_default_value_full(
638 val_str,
639 field_type,
640 available_consts,
641 type_aliases,
642 resolved_imports,
643 ) {
644 map.insert(key.to_string(), v);
645 }
646 }
647 }
648 Value::Struct(Arc::new(map))
649}
650
651fn resolve_const_default(name: &str, available_consts: &HashMap<String, Value>) -> Option<Value> {
657 let name = name.trim();
658 if name.is_empty() {
659 return None;
660 }
661 available_consts.get(name).cloned()
662}
663
664fn resolve_kinds_default(
666 expr: &str,
667 type_aliases: &HashMap<String, VarType>,
668 resolved_imports: &HashMap<String, ImportedNamespace>,
669) -> Option<Value> {
670 let s = expr.trim();
671 let inner = s
672 .strip_prefix(crate::consts::FN_KINDS)?
673 .strip_prefix(crate::consts::PAREN_OPEN)?
674 .strip_suffix(crate::consts::PAREN_CLOSE)?
675 .trim();
676 if inner.is_empty() {
677 return None;
678 }
679 let var_type = if let Some(dot_pos) = inner.find(crate::consts::PATH_SEP) {
680 let ns_name = &inner[..dot_pos];
681 let type_name = &inner[dot_pos + 1..];
682 resolved_imports.get(ns_name)?.type_aliases.get(type_name)
683 } else {
684 type_aliases.get(inner)
685 };
686 if let Some(VarType::Enum(variants)) = var_type {
687 let list: Vec<Value> = variants
688 .iter()
689 .map(|v| Value::Str(v.name.clone()))
690 .collect();
691 Some(Value::List(Arc::new(list)))
692 } else {
693 None
694 }
695}
696
697#[cfg(test)]
708pub(crate) fn parse_default_value_with_type(
709 s: &str,
710 var_type: &VarType,
711 available_consts: &HashMap<String, Value>,
712) -> Option<Value> {
713 let empty_aliases = HashMap::new();
714 let empty_imports = HashMap::new();
715 parse_default_value_full(
716 s,
717 var_type,
718 available_consts,
719 &empty_aliases,
720 &empty_imports,
721 )
722}
723
724pub(crate) fn parse_default_value_full(
725 s: &str,
726 var_type: &VarType,
727 available_consts: &HashMap<String, Value>,
728 type_aliases: &HashMap<String, VarType>,
729 resolved_imports: &HashMap<String, ImportedNamespace>,
730) -> Option<Value> {
731 let s = s.trim();
732 if s.is_empty() {
733 return None;
734 }
735
736 if s.starts_with(crate::consts::BRACKET_OPEN) && s.ends_with(crate::consts::BRACKET_CLOSE) {
738 let inner = &s[1..s.len() - 1];
739 if inner.trim().is_empty() {
740 return Some(Value::List(Arc::new(Vec::new())));
741 }
742 let entries = split_at_depth_zero(inner);
743 let mut list = Vec::new();
744 let elem_type = match var_type {
745 VarType::List(fields) => {
746 if fields.len() == 1 && fields[0].name.is_empty() {
747 &fields[0].var_type
748 } else {
749 var_type
750 }
751 }
752 _ => var_type,
753 };
754 for e in entries {
755 if let Some(v) = parse_default_value_full(
756 e,
757 elem_type,
758 available_consts,
759 type_aliases,
760 resolved_imports,
761 ) {
762 list.push(v);
763 }
764 }
765 return Some(Value::List(Arc::new(list)));
766 }
767
768 if s.starts_with('{') && s.ends_with('}') {
770 let inner = &s[1..s.len() - 1].trim();
771 if inner.is_empty() {
772 return match var_type {
773 VarType::Struct(_) => Some(Value::Struct(Arc::new(HashMap::new()))),
774 _ => None,
775 };
776 }
777
778 let fields = match var_type {
779 VarType::Struct(f) | VarType::List(f) => f.as_slice(),
780 _ => &[],
781 };
782 return Some(parse_struct_default(
783 inner,
784 fields,
785 available_consts,
786 type_aliases,
787 resolved_imports,
788 ));
789 }
790
791 if let Some(inner) = crate::consts::strip_string_literal(s) {
793 return Some(Value::Str(inner.to_string()));
794 }
795
796 if s == crate::consts::LIT_TRUE {
798 return Some(Value::Bool(true));
799 }
800 if s == crate::consts::LIT_FALSE {
801 return Some(Value::Bool(false));
802 }
803
804 if let Ok(n) = s.parse::<i64>() {
806 return Some(Value::Int(n));
807 }
808
809 if let Ok(n) = s.parse::<f64>() {
811 return Some(Value::Float(n));
812 }
813
814 if let VarType::Option(inner) = var_type {
817 if s == crate::consts::OPTION_NONE {
818 return Some(Value::None);
819 }
820 return parse_default_value_full(
821 s,
822 inner,
823 available_consts,
824 type_aliases,
825 resolved_imports,
826 );
827 }
828
829 if let VarType::Enum(variants) = var_type {
831 return parse_enum_default_value(
832 s,
833 variants,
834 available_consts,
835 type_aliases,
836 resolved_imports,
837 );
838 }
839
840 if let Some(val) = resolve_const_default(s, available_consts) {
841 return Some(val);
842 }
843 if let Some(val) = resolve_kinds_default(s, type_aliases, resolved_imports) {
844 return Some(val);
845 }
846
847 None
850}
851
852fn parse_enum_default_value(
855 s: &str,
856 variants: &[crate::types::VariantDecl],
857 available_consts: &HashMap<String, Value>,
858 type_aliases: &HashMap<String, VarType>,
859 resolved_imports: &HashMap<String, ImportedNamespace>,
860) -> Option<Value> {
861 if let Some(open_pos) = s.find(crate::consts::PAREN_OPEN) {
865 if s.ends_with(crate::consts::PAREN_CLOSE) {
866 let variant_name = s[..open_pos].trim();
867 let inner = &s[open_pos + 1..s.len() - 1];
868 let variant = variants.iter().find(|v| v.name == variant_name);
870 match variant {
871 Some(v) if v.fields.is_empty() => {
872 return None; }
874 Some(v) => {
875 let entries = split_at_depth_zero(inner);
877 let mut map = HashMap::new();
878 map.insert(
879 crate::consts::ENUM_TAG_KEY.to_string(),
880 Value::Str(variant_name.to_string()),
881 );
882 for e in entries {
883 let e = e.trim();
884 if e.is_empty() {
885 continue;
886 }
887 if let Some(eq_pos) = find_char_at_depth_zero(e, crate::consts::EQUALS) {
888 let key = e[..eq_pos].trim();
889 let val_str = e[eq_pos + 1..].trim();
890 let field_type = v
891 .fields
892 .iter()
893 .find(|f| f.name == key)
894 .map_or(&VarType::Str, |f| &f.var_type);
895 if let Some(val) = parse_default_value_full(
896 val_str,
897 field_type,
898 available_consts,
899 type_aliases,
900 resolved_imports,
901 ) {
902 map.insert(key.to_string(), val);
903 }
904 }
905 }
906 return Some(Value::Struct(Arc::new(map)));
907 }
908 None => return None, }
910 }
911 }
912
913 let variant = variants.iter().find(|v| v.name == s);
915 match variant {
916 Some(v) if !v.fields.is_empty() => {
917 None
919 }
920 Some(_) => Some(Value::Str(s.to_string())),
921 None => None, }
923}
924
925#[cfg(test)]
926pub(crate) fn parse_default_value(s: &str) -> Option<Value> {
927 parse_default_value_with_type(s, &VarType::Str, &HashMap::new())
928}
929
930#[cfg(test)]
931#[path = "params_tests.rs"]
932mod tests;