Skip to main content

devalang_wasm/language/syntax/parser/driver/statements/
advanced.rs

1use super::super::helpers::{parse_function_args, parse_map_value};
2use crate::language::syntax::ast::{Statement, StatementKind, Value};
3/// Advanced statement parsing: ArrowCall, Assign, Automate, Bind
4use 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
26/// Parse an arrow call: target -> method(args) -> method2(args2)
27/// Supports chaining multiple calls
28pub fn parse_arrow_call(line: &str, line_number: usize) -> Result<Statement> {
29    // Split by "->" to get chain of calls
30    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    // First part is the target
37    let target = parts[0].to_string();
38
39    // Parse method calls
40    let mut calls = Vec::new();
41
42    for method_call in &parts[1..] {
43        // Parse method(args) or just method
44        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            // Find matching closing paren
49            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            // Parse arguments
56            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            // Method without args
65            calls.push((normalize_arrow_method_name(method_call), Vec::new()));
66        }
67    }
68
69    // For now, we'll store all calls as separate ArrowCall statements
70    // or we can store them in a chain structure
71    // Let's store the first call and chain the rest
72
73    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    // Store chain in value for later processing
80    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    // Add remaining calls to chain
86    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
195/// Parse property assignment: target.property = value
196pub 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    // Split left into target.property
206    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    // Parse value
215    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
232/// Parse bind statement: bind source -> target { options }
233pub fn parse_bind(line: &str, line_number: usize) -> Result<Statement> {
234    // Supported forms:
235    //   bind <left> -> <right>
236    //   bind <left> -> <right> with { ... }
237    //   bind <left> with { ... } -> <right>
238    // We'll normalize to source/target and put the options (if any) into the statement value.
239
240    // Remove leading 'bind' and split on '->'
241    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    // Left side may contain 'with { ... }'
253    let left_raw = arrow_parts[0].trim();
254    let right_raw = arrow_parts[1].trim();
255
256    // Helper to extract optional 'with { ... }' from a side
257    fn extract_with(side: &str) -> Result<(String, Option<Value>)> {
258        let s = side.trim();
259        // Look for 'with {' pattern
260        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        // No 'with' found
274        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    // Merge options, favouring explicit right-side options (target) over left-side
281    let merged_options = match (left_opts, right_opts) {
282        (Some(mut l), Some(r)) => {
283            // merge maps: r overrides l
284            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}