devalang_wasm/language/syntax/parser/driver/statements/
advanced.rs1use super::super::helpers::{parse_function_args, parse_map_value};
2use crate::language::syntax::ast::{Statement, StatementKind, Value};
3use anyhow::{Result, anyhow};
5use std::collections::HashMap;
6
7fn normalize_arrow_method_name(method_name: &str) -> String {
8 match method_name.trim() {
9 "dur" | "len" => "duration",
10 "vel" => "velocity",
11 "amp" | "vol" | "volume" => "gain",
12 "wave" | "osc" => "waveform",
13 "env" | "envelope" => "adsr",
14 "verb" => "reverb",
15 "echo" => "delay",
16 "comp" => "compressor",
17 "lpf" => "lowpass",
18 "hpf" => "highpass",
19 "bpf" => "bandpass",
20 "dist" => "distortion",
21 other => other,
22 }
23 .to_string()
24}
25
26pub fn parse_arrow_call(line: &str, line_number: usize) -> Result<Statement> {
29 let parts: Vec<&str> = line.split("->").map(|s| s.trim()).collect();
31
32 if parts.len() < 2 {
33 return Err(anyhow!("Arrow call requires at least one '->' operator"));
34 }
35
36 let target = parts[0].to_string();
38
39 let mut calls = Vec::new();
41
42 for method_call in &parts[1..] {
43 if let Some(paren_idx) = method_call.find('(') {
45 let method_name = normalize_arrow_method_name(&method_call[..paren_idx]);
46 let args_str = &method_call[paren_idx + 1..];
47
48 let close_paren = args_str
50 .rfind(')')
51 .ok_or_else(|| anyhow!("Missing closing parenthesis in arrow call"))?;
52
53 let args_str = &args_str[..close_paren];
54
55 let args = if args_str.trim().is_empty() {
57 Vec::new()
58 } else {
59 parse_function_args(args_str)?
60 };
61
62 calls.push((method_name, args));
63 } else {
64 calls.push((normalize_arrow_method_name(method_call), Vec::new()));
66 }
67 }
68
69 if calls.is_empty() {
74 return Err(anyhow!("No method calls found in arrow call"));
75 }
76
77 let (method, args) = calls[0].clone();
78
79 let mut chain_value = HashMap::new();
81 chain_value.insert("target".to_string(), Value::String(target.clone()));
82 chain_value.insert("method".to_string(), Value::String(method.clone()));
83 chain_value.insert("args".to_string(), Value::Array(args.clone()));
84
85 if calls.len() > 1 {
87 let chain_calls: Vec<Value> = calls[1..]
88 .iter()
89 .map(|(m, a)| {
90 let mut call_map = HashMap::new();
91 call_map.insert("method".to_string(), Value::String(m.clone()));
92 call_map.insert("args".to_string(), Value::Array(a.clone()));
93 Value::Map(call_map)
94 })
95 .collect();
96
97 chain_value.insert("chain".to_string(), Value::Array(chain_calls));
98 }
99
100 Ok(Statement::new(
101 StatementKind::ArrowCall {
102 target,
103 method,
104 args,
105 },
106 Value::Map(chain_value),
107 0,
108 line_number,
109 1,
110 ))
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn test_arrow_aliases_are_normalized() {
119 let statement = parse_arrow_call(
120 r#"lead -> dur(1/4) -> vel(0.8) -> verb({ mix: 0.2 }) -> lpf({ cutoff: 900 })"#,
121 1,
122 )
123 .unwrap();
124
125 match statement.kind {
126 StatementKind::ArrowCall { method, .. } => {
127 assert_eq!(method, "duration");
128 }
129 other => panic!("expected arrow call, got {:?}", other),
130 }
131
132 match statement.value {
133 Value::Map(map) => {
134 assert!(matches!(
135 map.get("method"),
136 Some(Value::String(method)) if method == "duration"
137 ));
138
139 let chain = match map.get("chain") {
140 Some(Value::Array(chain)) => chain,
141 other => panic!("expected chain array, got {:?}", other),
142 };
143
144 assert!(matches!(
145 &chain[0],
146 Value::Map(call) if matches!(call.get("method"), Some(Value::String(method)) if method == "velocity")
147 ));
148 assert!(matches!(
149 &chain[1],
150 Value::Map(call) if matches!(call.get("method"), Some(Value::String(method)) if method == "reverb")
151 ));
152 assert!(matches!(
153 &chain[2],
154 Value::Map(call) if matches!(call.get("method"), Some(Value::String(method)) if method == "lowpass")
155 ));
156 }
157 other => panic!("expected arrow value map, got {:?}", other),
158 }
159 }
160
161 #[test]
162 fn test_synth_config_aliases_are_normalized() {
163 let statement = parse_arrow_call(
164 r#"synth -> wave("saw") -> env({ attack: 0.01 }) -> amp(0.8)"#,
165 1,
166 )
167 .unwrap();
168
169 match statement.value {
170 Value::Map(map) => {
171 assert!(matches!(
172 map.get("method"),
173 Some(Value::String(method)) if method == "waveform"
174 ));
175
176 let chain = match map.get("chain") {
177 Some(Value::Array(chain)) => chain,
178 other => panic!("expected chain array, got {:?}", other),
179 };
180
181 assert!(matches!(
182 &chain[0],
183 Value::Map(call) if matches!(call.get("method"), Some(Value::String(method)) if method == "adsr")
184 ));
185 assert!(matches!(
186 &chain[1],
187 Value::Map(call) if matches!(call.get("method"), Some(Value::String(method)) if method == "gain")
188 ));
189 }
190 other => panic!("expected arrow value map, got {:?}", other),
191 }
192 }
193}
194
195pub fn parse_assign(line: &str, line_number: usize) -> Result<Statement> {
197 let assign_parts: Vec<&str> = line.splitn(2, '=').collect();
198 if assign_parts.len() != 2 {
199 return Err(anyhow!("Invalid assignment syntax"));
200 }
201
202 let left = assign_parts[0].trim();
203 let right = assign_parts[1].trim();
204
205 let prop_parts: Vec<&str> = left.splitn(2, '.').collect();
207 if prop_parts.len() != 2 {
208 return Err(anyhow!("Assignment requires target.property syntax"));
209 }
210
211 let target = prop_parts[0].trim().to_string();
212 let property = prop_parts[1].trim().to_string();
213
214 let value = if let Ok(num) = right.parse::<f32>() {
216 Value::Number(num)
217 } else if right.starts_with('"') && right.ends_with('"') {
218 Value::String(right.trim_matches('"').to_string())
219 } else {
220 Value::Identifier(right.to_string())
221 };
222
223 Ok(Statement::new(
224 StatementKind::Assign { target, property },
225 value,
226 0,
227 line_number,
228 1,
229 ))
230}
231
232pub fn parse_bind(line: &str, line_number: usize) -> Result<Statement> {
234 let after_bind = line
242 .trim()
243 .strip_prefix("bind")
244 .ok_or_else(|| anyhow!("bind parsing error"))?
245 .trim();
246
247 let arrow_parts: Vec<&str> = after_bind.splitn(2, "->").collect();
248 if arrow_parts.len() != 2 {
249 return Err(anyhow!("bind requires source -> target syntax"));
250 }
251
252 let left_raw = arrow_parts[0].trim();
254 let right_raw = arrow_parts[1].trim();
255
256 fn extract_with(side: &str) -> Result<(String, Option<Value>)> {
258 let s = side.trim();
259 if let Some(with_pos) = s.find("with") {
261 let before = s[..with_pos].trim().to_string();
262 let after = s[with_pos + "with".len()..].trim();
263 if after.starts_with('{') {
264 if let Some(close_brace) = after.rfind('}') {
265 let options_str = &after[..close_brace + 1];
266 let options = parse_map_value(options_str)?;
267 return Ok((before, Some(options)));
268 } else {
269 return Err(anyhow!("unclosed brace in bind options"));
270 }
271 }
272 }
273 Ok((s.to_string(), None))
275 }
276
277 let (source_candidate, left_opts) = extract_with(left_raw)?;
278 let (target_candidate, right_opts) = extract_with(right_raw)?;
279
280 let merged_options = match (left_opts, right_opts) {
282 (Some(mut l), Some(r)) => {
283 if let (Value::Map(lm), Value::Map(rm)) = (&mut l, &r) {
285 for (k, v) in rm.iter() {
286 lm.insert(k.clone(), v.clone());
287 }
288 Some(l)
289 } else {
290 Some(r)
291 }
292 }
293 (Some(l), None) => Some(l),
294 (None, Some(r)) => Some(r),
295 (None, None) => None,
296 };
297
298 let source = source_candidate;
299 let target = target_candidate;
300
301 Ok(Statement::new(
302 StatementKind::Bind { source, target },
303 merged_options.unwrap_or(Value::Null),
304 0,
305 line_number,
306 1,
307 ))
308}