slash_lang/parser/
chain.rs1use crate::parser::ast::Arg;
2
3pub struct ChainParts {
5 pub name: String,
6 pub primary: Option<String>,
7 pub args: Vec<Arg>,
8}
9
10#[allow(clippy::result_unit_err)]
18pub fn parse_builder_chain(bare: &str) -> Result<ChainParts, ()> {
19 let s = bare.trim_start_matches('/');
20
21 let first_open = s.find('(');
24 let first_dot = find_dot_at_depth_zero(s);
25
26 if let Some(paren_pos) = first_open {
27 if first_dot.is_none() || paren_pos < first_dot.unwrap() {
28 let cmd_name = &s[..paren_pos];
30 let rest = &s[paren_pos..];
31
32 let (primary_val, remainder) = extract_balanced_parens(rest)?;
34
35 let primary = if primary_val.is_empty() {
36 None
37 } else {
38 Some(primary_val.to_string())
39 };
40
41 let args = if let Some(after_dot) = remainder.strip_prefix('.') {
42 parse_chain(after_dot)?
43 } else if remainder.is_empty() {
44 vec![]
45 } else {
46 return Err(());
47 };
48
49 return Ok(ChainParts {
50 name: cmd_name.to_string(),
51 primary,
52 args,
53 });
54 }
55 }
56
57 match s.split_once('.') {
59 Some((cmd_raw, chain)) => Ok(ChainParts {
60 name: cmd_raw.to_string(),
61 primary: None,
62 args: parse_chain(chain)?,
63 }),
64 None => Ok(ChainParts {
65 name: s.to_string(),
66 primary: None,
67 args: vec![],
68 }),
69 }
70}
71
72fn find_dot_at_depth_zero(s: &str) -> Option<usize> {
74 let mut depth: usize = 0;
75 for (i, ch) in s.char_indices() {
76 match ch {
77 '(' => depth += 1,
78 ')' => {
79 if depth == 0 {
80 return None;
81 }
82 depth -= 1;
83 }
84 '.' if depth == 0 => return Some(i),
85 _ => {}
86 }
87 }
88 None
89}
90
91fn extract_balanced_parens(s: &str) -> Result<(&str, &str), ()> {
94 debug_assert!(s.starts_with('('));
95 let mut depth: usize = 0;
96 for (i, ch) in s.char_indices() {
97 match ch {
98 '(' => depth += 1,
99 ')' => {
100 depth -= 1;
101 if depth == 0 {
102 let inner = &s[1..i];
103 let remainder = &s[i + 1..];
104 return Ok((inner, remainder));
105 }
106 }
107 _ => {}
108 }
109 }
110 Err(())
111}
112
113fn parse_chain(chain: &str) -> Result<Vec<Arg>, ()> {
114 split_segments(chain)?
115 .into_iter()
116 .filter(|s| !s.is_empty())
117 .map(parse_arg)
118 .collect()
119}
120
121fn split_segments(chain: &str) -> Result<Vec<&str>, ()> {
125 let mut segments = Vec::new();
126 let mut depth: usize = 0;
127 let mut start = 0;
128 for (i, ch) in chain.char_indices() {
129 match ch {
130 '(' => depth += 1,
131 ')' => {
132 if depth == 0 {
133 return Err(());
134 }
135 depth -= 1;
136 }
137 '.' if depth == 0 => {
138 segments.push(&chain[start..i]);
139 start = i + 1;
140 }
141 _ => {}
142 }
143 }
144 if depth != 0 {
145 return Err(());
146 }
147 segments.push(&chain[start..]);
148 Ok(segments)
149}
150
151fn parse_arg(segment: &str) -> Result<Arg, ()> {
157 if let Some((name, rest)) = segment.split_once('(') {
158 let value = rest.strip_suffix(')').ok_or(())?;
159 Ok(Arg {
160 name: name.to_string(),
161 value: if value.is_empty() {
162 None
163 } else {
164 Some(value.to_string())
165 },
166 })
167 } else {
168 Ok(Arg {
169 name: segment.to_string(),
170 value: None,
171 })
172 }
173}