1use alloc::{
7 boxed::Box,
8 string::{String, ToString},
9 sync::Arc,
10 vec::Vec,
11};
12
13use super::ImportedNamespace;
14use crate::{
15 compat::HashMap,
16 error::TemplateError,
17 types::{VarDecl, VarType},
18 value::Value,
19};
20
21pub(crate) fn join_continuation_lines(block: &str) -> Vec<String> {
24 let mut logical: Vec<String> = Vec::new();
25 for raw in block.lines() {
26 if raw.starts_with(' ') || raw.starts_with('\t') {
27 if let Some(prev) = logical.last_mut() {
29 prev.push(' ');
30 prev.push_str(raw.trim());
31 } else {
32 logical.push(raw.to_string());
33 }
34 } else {
35 logical.push(raw.to_string());
36 }
37 }
38 logical
39}
40
41pub(crate) fn parse_declarations(
46 rest: &str,
47 type_aliases: &HashMap<String, VarType>,
48 resolved_imports: &HashMap<String, ImportedNamespace>,
49 is_constant: bool,
50 available_consts: &HashMap<String, Value>,
51) -> Result<Vec<VarDecl>, TemplateError> {
52 let rest = rest.trim();
53 if rest.is_empty() {
54 return Ok(vec![]);
56 }
57
58 let inner = rest
60 .strip_prefix(crate::consts::BRACKET_OPEN)
61 .and_then(|s| s.strip_suffix(crate::consts::BRACKET_CLOSE))
62 .unwrap_or(rest);
63
64 let entries = if inner.contains("- ") {
67 let mut result = Vec::new();
70 for part in inner.split(" - ") {
71 let part = part.trim().strip_prefix('-').unwrap_or(part).trim();
72 if !part.is_empty() {
73 result.push(part.to_string());
74 }
75 }
76 result
77 } else {
78 split_at_depth_zero(inner)
80 .into_iter()
81 .map(ToString::to_string)
82 .collect()
83 };
84
85 let mut decls = Vec::new();
86 let mut seen_names = crate::compat::HashSet::new();
87 let mut current_consts = available_consts.clone();
88 for entry in &entries {
89 let e = entry.trim();
90 let trimmed = crate::consts::strip_string_literal(e).unwrap_or(e).trim();
91 if let Some(decl) = parse_single_declaration(
92 trimmed,
93 type_aliases,
94 resolved_imports,
95 is_constant,
96 &mut current_consts,
97 &mut seen_names,
98 )? {
99 decls.push(decl);
100 }
101 }
102
103 Ok(decls)
104}
105
106fn parse_single_declaration(
108 trimmed: &str,
109 type_aliases: &HashMap<String, VarType>,
110 resolved_imports: &HashMap<String, ImportedNamespace>,
111 is_constant: bool,
112 current_consts: &mut HashMap<String, Value>,
113 seen_names: &mut crate::compat::HashSet<String>,
114) -> Result<Option<VarDecl>, TemplateError> {
115 if trimmed.is_empty() {
116 return Ok(None);
117 }
118
119 let Some(eq_pos) = find_char_at_depth_zero(trimmed, crate::consts::EQUALS) else {
121 let label = if is_constant { "constant" } else { "param" };
122 return Err(TemplateError::syntax(format!(
123 "{label} '{trimmed}' is missing a type annotation (expected 'name = type')"
124 )));
125 };
126
127 let name = trimmed[..eq_pos].trim().to_string();
128 let type_and_default = trimmed[eq_pos + 1..].trim();
129
130 if !seen_names.insert(name.clone()) {
132 let err = if is_constant {
133 crate::consts::ERR_DUPLICATE_CONST
134 } else {
135 crate::consts::ERR_DUPLICATE_PARAM
136 };
137 return Err(TemplateError::syntax(format!("{err}: '{name}'")));
138 }
139
140 if crate::consts::RESERVED_NAMES.contains(&name.as_str()) {
142 return Err(TemplateError::syntax(format!(
143 "{}: '{name}'",
144 crate::consts::ERR_RESERVED_KEYWORD
145 )));
146 }
147
148 let (type_str, default_part) =
150 if let Some(assign_pos) = find_assign_default_at_depth_zero(type_and_default) {
151 (
152 type_and_default[..assign_pos].trim(),
153 Some(type_and_default[assign_pos + 2..].trim()),
154 )
155 } else {
156 (type_and_default, None)
157 };
158
159 let var_type = parse_type_annotation(type_str, type_aliases, resolved_imports)
160 .map_err(|e| TemplateError::syntax(format!("declaration '{name}': {e}")))?;
161
162 let default_value = if let Some(dp) = default_part {
163 let default = parse_default_value_full(
164 dp,
165 &var_type,
166 current_consts,
167 type_aliases,
168 resolved_imports,
169 )
170 .or_else(|| resolve_const_default(dp, current_consts))
171 .or_else(|| resolve_kinds_default(dp, type_aliases, resolved_imports))
172 .ok_or_else(|| {
173 TemplateError::syntax(format!(
174 "invalid default value '{dp}' for declaration '{name}' (strings must be quoted)"
175 ))
176 })?;
177 current_consts.insert(name.clone(), default.clone());
178 Some(default)
179 } else {
180 None
181 };
182
183 if is_constant && default_value.is_none() {
185 return Err(TemplateError::syntax(format!(
186 "constant '{name}' is missing a value (expected 'name = type := value')"
187 )));
188 }
189
190 if let Some(ref default) = default_value
192 && !var_type.matches(default)
193 {
194 let label = if is_constant { "constant" } else { "param" };
195 return Err(TemplateError::syntax(format!(
196 "{label} '{name}': value has type '{}' but declared type is '{var_type}'",
197 default.type_name()
198 )));
199 }
200
201 Ok(Some(VarDecl {
202 name,
203 var_type,
204 default_value,
205 }))
206}
207
208pub(crate) fn strip_type_brackets(s: &str) -> Option<&str> {
212 if let (Some(inner), true) = (
213 s.strip_prefix(crate::consts::PAREN_OPEN),
214 s.ends_with(crate::consts::PAREN_CLOSE),
215 ) {
216 Some(&inner[..inner.len() - 1])
217 } else {
218 None
219 }
220}
221
222pub(crate) fn split_at_depth_zero(input: &str) -> Vec<&str> {
224 use crate::consts::{
225 ANGLE_CLOSE, ANGLE_OPEN, BRACE_CLOSE, BRACE_OPEN, BRACKET_CLOSE, BRACKET_OPEN, COMMA,
226 PAREN_CLOSE, PAREN_OPEN,
227 };
228 let mut entries = Vec::new();
229 let mut depth: u32 = 0;
230 let mut start = 0;
231 for (i, ch) in input.char_indices() {
232 match ch {
233 ANGLE_OPEN | BRACKET_OPEN | PAREN_OPEN | BRACE_OPEN => depth += 1,
234 ANGLE_CLOSE | BRACKET_CLOSE | PAREN_CLOSE | BRACE_CLOSE => {
235 depth = depth.saturating_sub(1);
236 }
237 COMMA if depth == 0 => {
238 entries.push(&input[start..i]);
239 start = i + 1;
240 }
241 _ => {}
242 }
243 }
244 entries.push(&input[start..]);
245 entries
246}
247
248pub(crate) fn find_char_at_depth_zero(input: &str, target: char) -> Option<usize> {
250 use crate::consts::{
251 ANGLE_CLOSE, ANGLE_OPEN, BRACE_CLOSE, BRACE_OPEN, BRACKET_CLOSE, BRACKET_OPEN, PAREN_CLOSE,
252 PAREN_OPEN,
253 };
254 let mut depth: u32 = 0;
255 for (i, ch) in input.char_indices() {
256 match ch {
257 ANGLE_OPEN | BRACKET_OPEN | PAREN_OPEN | BRACE_OPEN => depth += 1,
258 ANGLE_CLOSE | BRACKET_CLOSE | PAREN_CLOSE | BRACE_CLOSE => {
259 depth = depth.saturating_sub(1);
260 }
261 c if c == target && depth == 0 => return Some(i),
262 _ => {}
263 }
264 }
265 None
266}
267
268fn find_assign_default_at_depth_zero(input: &str) -> Option<usize> {
270 use crate::consts::{
271 ANGLE_CLOSE_BYTE, ANGLE_OPEN_BYTE, BRACE_CLOSE_BYTE, BRACE_OPEN_BYTE, BRACKET_CLOSE_BYTE,
272 BRACKET_OPEN_BYTE, COLON_BYTE, EQUALS_BYTE, PAREN_CLOSE_BYTE, PAREN_OPEN_BYTE,
273 };
274 let mut depth: u32 = 0;
275 let bytes = input.as_bytes();
276 for (i, &b) in bytes.iter().enumerate() {
277 match b {
278 ANGLE_OPEN_BYTE | BRACKET_OPEN_BYTE | PAREN_OPEN_BYTE | BRACE_OPEN_BYTE => depth += 1,
279 ANGLE_CLOSE_BYTE | BRACKET_CLOSE_BYTE | PAREN_CLOSE_BYTE | BRACE_CLOSE_BYTE => {
280 depth = depth.saturating_sub(1);
281 }
282 COLON_BYTE if depth == 0 && bytes.get(i + 1) == Some(&EQUALS_BYTE) => return Some(i),
283 _ => {}
284 }
285 }
286 None
287}
288
289fn starts_with_compound_type(s: &str, keyword: &str) -> bool {
305 if let Some(rest) = s.strip_prefix(keyword) {
306 let rest = rest.trim_start();
307 rest.starts_with(crate::consts::PAREN_OPEN)
308 } else {
309 false
310 }
311}
312
313pub fn parse_type_annotation(
318 s: &str,
319 type_aliases: &HashMap<String, VarType>,
320 resolved_imports: &HashMap<String, ImportedNamespace>,
321) -> Result<VarType, String> {
322 use crate::consts::{
323 ANGLE_OPEN, BRACKET_OPEN, ERR_COMPOUND_BRACKETS_PROHIBITED, TYPE_BOOL, TYPE_ENUM,
324 TYPE_FLOAT, TYPE_INT, TYPE_LIST, TYPE_OPTION, TYPE_STR, TYPE_STRUCT, TYPE_TMPL,
325 };
326
327 let s = crate::consts::strip_string_literal(s.trim())
328 .unwrap_or(s.trim())
329 .trim();
330
331 for kw in &[TYPE_LIST, TYPE_STRUCT, TYPE_ENUM, TYPE_TMPL, TYPE_OPTION] {
332 if let Some(rest) = s.strip_prefix(kw) {
333 let rest_trimmed = rest.trim_start();
334 if rest_trimmed.starts_with(ANGLE_OPEN) || rest_trimmed.starts_with(BRACKET_OPEN) {
335 return Err(format!(
336 "compound type '{kw}': {ERR_COMPOUND_BRACKETS_PROHIBITED}"
337 ));
338 }
339 }
340 }
341
342 if let Some(ty) = type_aliases.get(s) {
344 return Ok(ty.clone());
345 }
346
347 if let Some(dot_pos) = s.find(crate::consts::PATH_SEP) {
349 let stem = &s[..dot_pos];
350 let type_name = &s[dot_pos + 1..];
351 if let Some(ns) = resolved_imports.get(stem) {
352 if let Some(ty) = ns.type_aliases.get(type_name) {
353 return Ok(ty.clone());
354 }
355 if let Some(ty) = ns.param_types.get(type_name) {
356 return Ok(ty.clone());
357 }
358 return Err(format!("import '{stem}' has no type '{type_name}'"));
359 }
360 }
361
362 if s == TYPE_STR {
363 Ok(VarType::Str)
364 } else if s == TYPE_BOOL {
365 Ok(VarType::Bool)
366 } else if s == TYPE_INT {
367 Ok(VarType::Int)
368 } else if s == TYPE_FLOAT {
369 Ok(VarType::Float)
370 } else if starts_with_compound_type(s, TYPE_LIST) {
371 parse_compound_type_list(s, type_aliases, resolved_imports)
372 } else if starts_with_compound_type(s, TYPE_STRUCT) {
373 parse_compound_type_struct(s, type_aliases, resolved_imports)
374 } else if starts_with_compound_type(s, TYPE_ENUM) {
375 parse_enum_type(s, type_aliases, resolved_imports)
376 } else if starts_with_compound_type(s, TYPE_TMPL) {
377 parse_tmpl_type(s, type_aliases, resolved_imports)
378 } else if starts_with_compound_type(s, TYPE_OPTION) {
379 parse_option_type(s, type_aliases, resolved_imports)
380 } else {
381 Err(format!("unknown type '{s}'"))
382 }
383}
384
385fn parse_enum_type(
387 s: &str,
388 type_aliases: &HashMap<String, VarType>,
389 resolved_imports: &HashMap<String, ImportedNamespace>,
390) -> Result<VarType, String> {
391 use crate::{consts::TYPE_ENUM, types::VariantDecl};
392
393 let rest = s.strip_prefix(TYPE_ENUM).unwrap_or("").trim();
394 let Some(inner) = strip_type_brackets(rest) else {
395 return Err(format!("malformed enum type: '{s}'"));
396 };
397 let entries = split_at_depth_zero(inner);
398 let mut variants = Vec::new();
399 for entry in entries {
400 let entry = entry.trim();
401 if entry.is_empty() {
402 continue;
403 }
404 if let (Some(open_idx), Some(close_idx)) = (
405 entry.find(crate::consts::PAREN_OPEN),
406 entry.rfind(crate::consts::PAREN_CLOSE),
407 ) {
408 let name = entry[..open_idx].trim().to_string();
409 let fields_str = &entry[open_idx + 1..close_idx];
410 let fields = parse_field_declarations(fields_str, type_aliases, resolved_imports)?;
411 if fields.iter().any(|f| f.name.is_empty()) {
412 return Err(
413 "enum struct variant must use named fields (e.g. Variant(name = str))"
414 .to_string(),
415 );
416 }
417 variants.push(VariantDecl { name, fields });
418 continue;
419 }
420 variants.push(VariantDecl {
421 name: entry.to_string(),
422 fields: vec![],
423 });
424 }
425 if variants.is_empty() {
426 return Err("enum must have at least one variant".to_string());
427 }
428 for v in &variants {
430 if crate::consts::RESERVED_NAMES.contains(&v.name.as_str()) {
431 return Err(format!(
432 "enum variant name '{}' shadows a builtin type keyword",
433 v.name
434 ));
435 }
436 }
437 Ok(VarType::Enum(variants))
438}
439
440fn parse_compound_type_list(
442 s: &str,
443 type_aliases: &HashMap<String, VarType>,
444 resolved_imports: &HashMap<String, ImportedNamespace>,
445) -> Result<VarType, String> {
446 use crate::consts::TYPE_LIST;
447
448 let rest = s.strip_prefix(TYPE_LIST).unwrap_or("").trim();
449 let Some(inner) = strip_type_brackets(rest) else {
450 return Err(format!("malformed list type: '{s}'"));
451 };
452 let fields = parse_field_declarations(inner, type_aliases, resolved_imports)?;
453 if fields.is_empty() {
454 return Err("untyped list() is not allowed; must specify element type or fields (e.g., list(str) or list(name = str))".to_string());
455 }
456 if fields.len() > 1 && fields.iter().any(|f| f.name.is_empty()) {
457 return Err(
458 "list with multiple fields must use named fields (e.g. list(name = str, count = int))"
459 .to_string(),
460 );
461 }
462 let inner_trimmed = inner.trim();
465 if inner_trimmed.starts_with(crate::consts::TYPE_STRUCT_ANGLE_PREFIX)
466 || inner_trimmed.starts_with(crate::consts::TYPE_STRUCT_PREFIX)
467 || inner_trimmed.starts_with(crate::consts::TYPE_STRUCT_BRACKET_PREFIX)
468 || inner_trimmed.starts_with(crate::consts::TYPE_STRUCT_SPACE_PREFIX)
469 {
470 return Err(
471 "list(struct(..)) is redundant; use named fields directly: list(name = str, count = int)"
472 .to_string(),
473 );
474 }
475 if fields.len() == 1 && fields[0].name.is_empty() {
478 if let VarType::Struct(ref struct_fields) = fields[0].var_type {
479 return Ok(VarType::List(struct_fields.clone()));
480 }
481 }
482 Ok(VarType::List(fields))
483}
484
485fn parse_compound_type_struct(
487 s: &str,
488 type_aliases: &HashMap<String, VarType>,
489 resolved_imports: &HashMap<String, ImportedNamespace>,
490) -> Result<VarType, String> {
491 use crate::consts::TYPE_STRUCT;
492
493 let rest = s.strip_prefix(TYPE_STRUCT).unwrap_or("").trim();
494 let Some(inner) = strip_type_brackets(rest) else {
495 return Err(format!("malformed struct type: '{s}'"));
496 };
497 let fields = parse_field_declarations(inner, type_aliases, resolved_imports)?;
498 if fields.is_empty() {
499 return Err(
500 "untyped struct() is not allowed; must specify fields (e.g., struct(name = str))"
501 .to_string(),
502 );
503 }
504 if fields.iter().any(|f| f.name.is_empty()) {
505 return Err(
506 "struct must use named fields (e.g. struct(name = str, count = int))".to_string(),
507 );
508 }
509 Ok(VarType::Struct(fields))
510}
511
512fn parse_tmpl_type(
514 s: &str,
515 type_aliases: &HashMap<String, VarType>,
516 resolved_imports: &HashMap<String, ImportedNamespace>,
517) -> Result<VarType, String> {
518 use crate::consts::TYPE_TMPL;
519
520 let rest = s.strip_prefix(TYPE_TMPL).unwrap_or("").trim();
521 let Some(inner) = strip_type_brackets(rest) else {
522 return Err(format!("malformed tmpl type: '{s}'"));
523 };
524 let fields = parse_field_declarations(inner, type_aliases, resolved_imports)?;
525 if fields.iter().any(|f| f.name.is_empty()) {
526 return Err("tmpl must use named fields (e.g. tmpl(name = str, count = int))".to_string());
527 }
528 Ok(VarType::Tmpl(fields))
529}
530
531fn parse_option_type(
533 s: &str,
534 type_aliases: &HashMap<String, VarType>,
535 resolved_imports: &HashMap<String, ImportedNamespace>,
536) -> Result<VarType, String> {
537 use crate::consts::TYPE_OPTION;
538
539 let rest = s.strip_prefix(TYPE_OPTION).unwrap_or("").trim();
540 let Some(inner) = strip_type_brackets(rest) else {
541 return Err(format!("malformed option type: '{s}'"));
542 };
543 let inner = inner.trim();
544 if inner.is_empty() {
545 return Err("option() requires an inner type (e.g. option(str))".to_string());
546 }
547 let inner_type = parse_type_annotation(inner, type_aliases, resolved_imports)?;
548 Ok(VarType::Option(Box::new(inner_type)))
549}
550
551fn parse_field_declarations(
553 inner: &str,
554 type_aliases: &HashMap<String, VarType>,
555 resolved_imports: &HashMap<String, ImportedNamespace>,
556) -> Result<Vec<VarDecl>, String> {
557 let entries = split_at_depth_zero(inner);
558 let mut decls = Vec::new();
559 for f in &entries {
560 let f = f.trim();
561 if f.is_empty() {
562 continue;
563 }
564 let (name, type_str) =
565 if let Some(eq_pos) = find_char_at_depth_zero(f, crate::consts::EQUALS) {
566 (f[..eq_pos].trim().to_string(), f[eq_pos + 1..].trim())
567 } else {
568 (String::new(), f)
569 };
570 let var_type = parse_type_annotation(type_str, type_aliases, resolved_imports)?;
571 decls.push(VarDecl {
572 name,
573 var_type,
574 default_value: None,
575 });
576 }
577 Ok(decls)
578}
579
580fn parse_struct_default(
586 inner: &str,
587 fields: &[VarDecl],
588 available_consts: &HashMap<String, Value>,
589 type_aliases: &HashMap<String, VarType>,
590 resolved_imports: &HashMap<String, ImportedNamespace>,
591) -> Value {
592 let entries = split_at_depth_zero(inner);
593 let mut map = HashMap::new();
594 for e in entries {
595 let e = e.trim();
596 if e.is_empty() {
597 continue;
598 }
599 if let Some(eq_pos) = find_char_at_depth_zero(e, crate::consts::EQUALS) {
600 let key = e[..eq_pos].trim();
601 let val_str = e[eq_pos + 1..].trim();
602 let field_type = fields
603 .iter()
604 .find(|d| d.name == key)
605 .map_or(&VarType::Str, |d| &d.var_type);
606 if let Some(v) = parse_default_value_full(
607 val_str,
608 field_type,
609 available_consts,
610 type_aliases,
611 resolved_imports,
612 ) {
613 map.insert(key.to_string(), v);
614 }
615 }
616 }
617 Value::Struct(Arc::new(map))
618}
619
620fn resolve_const_default(name: &str, available_consts: &HashMap<String, Value>) -> Option<Value> {
626 let name = name.trim();
627 if name.is_empty() {
628 return None;
629 }
630 available_consts.get(name).cloned()
631}
632
633fn resolve_kinds_default(
635 expr: &str,
636 type_aliases: &HashMap<String, VarType>,
637 resolved_imports: &HashMap<String, ImportedNamespace>,
638) -> Option<Value> {
639 let s = expr.trim();
640 let inner = s
641 .strip_prefix(crate::consts::FN_KINDS)?
642 .strip_prefix(crate::consts::PAREN_OPEN)?
643 .strip_suffix(crate::consts::PAREN_CLOSE)?
644 .trim();
645 if inner.is_empty() {
646 return None;
647 }
648 let var_type = if let Some(dot_pos) = inner.find(crate::consts::PATH_SEP) {
649 let ns_name = &inner[..dot_pos];
650 let type_name = &inner[dot_pos + 1..];
651 resolved_imports.get(ns_name)?.type_aliases.get(type_name)
652 } else {
653 type_aliases.get(inner)
654 };
655 if let Some(VarType::Enum(variants)) = var_type {
656 let list: Vec<Value> = variants
657 .iter()
658 .map(|v| Value::Str(v.name.clone()))
659 .collect();
660 Some(Value::List(Arc::new(list)))
661 } else {
662 None
663 }
664}
665
666#[cfg(test)]
677pub(crate) fn parse_default_value_with_type(
678 s: &str,
679 var_type: &VarType,
680 available_consts: &HashMap<String, Value>,
681) -> Option<Value> {
682 let empty_aliases = HashMap::new();
683 let empty_imports = HashMap::new();
684 parse_default_value_full(
685 s,
686 var_type,
687 available_consts,
688 &empty_aliases,
689 &empty_imports,
690 )
691}
692
693pub(crate) fn parse_default_value_full(
694 s: &str,
695 var_type: &VarType,
696 available_consts: &HashMap<String, Value>,
697 type_aliases: &HashMap<String, VarType>,
698 resolved_imports: &HashMap<String, ImportedNamespace>,
699) -> Option<Value> {
700 let s = s.trim();
701 if s.is_empty() {
702 return None;
703 }
704
705 if s.starts_with(crate::consts::BRACKET_OPEN) && s.ends_with(crate::consts::BRACKET_CLOSE) {
707 let inner = &s[1..s.len() - 1];
708 if inner.trim().is_empty() {
709 return Some(Value::List(Arc::new(Vec::new())));
710 }
711 let entries = split_at_depth_zero(inner);
712 let mut list = Vec::new();
713 let elem_type = match var_type {
714 VarType::List(fields) => {
715 if fields.len() == 1 && fields[0].name.is_empty() {
716 &fields[0].var_type
717 } else {
718 var_type
719 }
720 }
721 _ => var_type,
722 };
723 for e in entries {
724 if let Some(v) = parse_default_value_full(
725 e,
726 elem_type,
727 available_consts,
728 type_aliases,
729 resolved_imports,
730 ) {
731 list.push(v);
732 }
733 }
734 return Some(Value::List(Arc::new(list)));
735 }
736
737 if s.starts_with('{') && s.ends_with('}') {
739 let inner = &s[1..s.len() - 1].trim();
740 if inner.is_empty() {
741 return match var_type {
742 VarType::Struct(_) => Some(Value::Struct(Arc::new(HashMap::new()))),
743 _ => None,
744 };
745 }
746
747 let fields = match var_type {
748 VarType::Struct(f) | VarType::List(f) => f.as_slice(),
749 _ => &[],
750 };
751 return Some(parse_struct_default(
752 inner,
753 fields,
754 available_consts,
755 type_aliases,
756 resolved_imports,
757 ));
758 }
759
760 if let Some(inner) = crate::consts::strip_string_literal(s) {
762 return Some(Value::Str(inner.to_string()));
763 }
764
765 if s == crate::consts::LIT_TRUE {
767 return Some(Value::Bool(true));
768 }
769 if s == crate::consts::LIT_FALSE {
770 return Some(Value::Bool(false));
771 }
772
773 if let Ok(n) = s.parse::<i64>() {
775 return Some(Value::Int(n));
776 }
777
778 if let Ok(n) = s.parse::<f64>() {
780 return Some(Value::Float(n));
781 }
782
783 if let VarType::Option(inner) = var_type {
786 if s == crate::consts::OPTION_NONE {
787 return Some(Value::None);
788 }
789 return parse_default_value_full(
790 s,
791 inner,
792 available_consts,
793 type_aliases,
794 resolved_imports,
795 );
796 }
797
798 if let VarType::Enum(variants) = var_type {
800 return parse_enum_default_value(
801 s,
802 variants,
803 available_consts,
804 type_aliases,
805 resolved_imports,
806 );
807 }
808
809 if let Some(val) = resolve_const_default(s, available_consts) {
810 return Some(val);
811 }
812 if let Some(val) = resolve_kinds_default(s, type_aliases, resolved_imports) {
813 return Some(val);
814 }
815
816 None
819}
820
821fn parse_enum_default_value(
824 s: &str,
825 variants: &[crate::types::VariantDecl],
826 available_consts: &HashMap<String, Value>,
827 type_aliases: &HashMap<String, VarType>,
828 resolved_imports: &HashMap<String, ImportedNamespace>,
829) -> Option<Value> {
830 if let Some(open_pos) = s.find(crate::consts::PAREN_OPEN) {
834 if s.ends_with(crate::consts::PAREN_CLOSE) {
835 let variant_name = s[..open_pos].trim();
836 let inner = &s[open_pos + 1..s.len() - 1];
837 let variant = variants.iter().find(|v| v.name == variant_name);
839 match variant {
840 Some(v) if v.fields.is_empty() => {
841 return None; }
843 Some(v) => {
844 let entries = split_at_depth_zero(inner);
846 let mut map = HashMap::new();
847 map.insert(
848 crate::consts::ENUM_TAG_KEY.to_string(),
849 Value::Str(variant_name.to_string()),
850 );
851 for e in entries {
852 let e = e.trim();
853 if e.is_empty() {
854 continue;
855 }
856 if let Some(eq_pos) = find_char_at_depth_zero(e, crate::consts::EQUALS) {
857 let key = e[..eq_pos].trim();
858 let val_str = e[eq_pos + 1..].trim();
859 let field_type = v
860 .fields
861 .iter()
862 .find(|f| f.name == key)
863 .map_or(&VarType::Str, |f| &f.var_type);
864 if let Some(val) = parse_default_value_full(
865 val_str,
866 field_type,
867 available_consts,
868 type_aliases,
869 resolved_imports,
870 ) {
871 map.insert(key.to_string(), val);
872 }
873 }
874 }
875 return Some(Value::Struct(Arc::new(map)));
876 }
877 None => return None, }
879 }
880 }
881
882 let variant = variants.iter().find(|v| v.name == s);
884 match variant {
885 Some(v) if !v.fields.is_empty() => {
886 None
888 }
889 Some(_) => Some(Value::Str(s.to_string())),
890 None => None, }
892}
893
894#[cfg(test)]
895pub(crate) fn parse_default_value(s: &str) -> Option<Value> {
896 parse_default_value_with_type(s, &VarType::Str, &HashMap::new())
897}
898
899#[cfg(test)]
900#[path = "params_tests.rs"]
901mod tests;