devalang_wasm/language/syntax/parser/driver/
helpers.rs

1use crate::language::syntax::ast::Value;
2/// Helper functions for parsing values, arguments, and complex structures
3use anyhow::Result;
4use std::collections::HashMap;
5
6/// Parse function arguments from string
7/// Supports: numbers, strings, identifiers, arrays, maps
8pub fn parse_function_args(args_str: &str) -> Result<Vec<Value>> {
9    let mut args = Vec::new();
10    let mut current_arg = String::new();
11    let mut depth = 0; // Track nested structures
12    let mut in_string = false;
13
14    for ch in args_str.chars() {
15        match ch {
16            '"' => {
17                in_string = !in_string;
18                current_arg.push(ch);
19            }
20            '[' | '{' if !in_string => {
21                depth += 1;
22                current_arg.push(ch);
23            }
24            ']' | '}' if !in_string => {
25                depth -= 1;
26                current_arg.push(ch);
27            }
28            ',' if depth == 0 && !in_string => {
29                // End of argument
30                if !current_arg.trim().is_empty() {
31                    args.push(parse_single_arg(current_arg.trim())?);
32                    current_arg.clear();
33                }
34            }
35            _ => {
36                current_arg.push(ch);
37            }
38        }
39    }
40
41    // Last argument
42    if !current_arg.trim().is_empty() {
43        args.push(parse_single_arg(current_arg.trim())?);
44    }
45
46    Ok(args)
47}
48
49/// Parse a single argument value
50pub fn parse_single_arg(arg: &str) -> Result<Value> {
51    let arg = arg.trim();
52
53    // String literal
54    if arg.starts_with('"') && arg.ends_with('"') {
55        return Ok(Value::String(arg[1..arg.len() - 1].to_string()));
56    }
57
58    // Array
59    if arg.starts_with('[') && arg.ends_with(']') {
60        let inner = &arg[1..arg.len() - 1];
61        let items = parse_function_args(inner)?;
62        return Ok(Value::Array(items));
63    }
64
65    // Map/Object
66    if arg.starts_with('{') && arg.ends_with('}') {
67        let inner = &arg[1..arg.len() - 1];
68        let mut map = HashMap::new();
69
70        // Parse key: value pairs
71        for pair in inner.split(',') {
72            if let Some(colon_idx) = pair.find(':') {
73                let key = pair[..colon_idx].trim().trim_matches('"');
74                let value = parse_single_arg(pair[colon_idx + 1..].trim())?;
75                map.insert(key.to_string(), value);
76            }
77        }
78
79        return Ok(Value::Map(map));
80    }
81
82    // Number
83    if let Ok(num) = arg.parse::<f32>() {
84        return Ok(Value::Number(num));
85    }
86
87    // Boolean
88    match arg.to_lowercase().as_str() {
89        "true" => return Ok(Value::Boolean(true)),
90        "false" => return Ok(Value::Boolean(false)),
91        _ => {}
92    }
93
94    // Default to identifier
95    Ok(Value::Identifier(arg.to_string()))
96}
97
98/// Parse synth definition: synth waveform { params } OR synth plugin.<name> { params }
99/// Returns a Map with type="synth", waveform/plugin info, and parameters
100/// 
101/// Supported syntaxes:
102/// - synth "sine" { attack: 0.1 }
103/// - synth plugin.acid.synth { waveform: "sine" }
104/// - synth { waveform: "sine", attack: 0.1 }  // waveform in params
105pub fn parse_synth_definition(input: &str) -> Result<Value> {
106    // Remove "synth " prefix
107    let input = input.trim_start_matches("synth ").trim();
108
109    // Check if we have braces
110    let (waveform_or_plugin, params_str) = if let Some(brace_idx) = input.find('{') {
111        let before_brace = input[..brace_idx].trim();
112        let params = &input[brace_idx..];
113        (before_brace, params)
114    } else {
115        // No parameters, just waveform or plugin reference
116        return Ok(Value::Map({
117            let mut map = HashMap::new();
118            map.insert("type".to_string(), Value::String("synth".to_string()));
119            
120            // Check if it's a plugin reference (plugin.author.name)
121            if input.starts_with("plugin.") {
122                let parts: Vec<&str> = input.split('.').collect();
123                if parts.len() >= 3 {
124                    map.insert("plugin_author".to_string(), Value::String(parts[1].to_string()));
125                    map.insert("plugin_name".to_string(), Value::String(parts[2].to_string()));
126                    if parts.len() >= 4 {
127                        map.insert("plugin_export".to_string(), Value::String(parts[3].to_string()));
128                    }
129                }
130            } else {
131                map.insert("waveform".to_string(), Value::String(input.to_string()));
132            }
133            
134            map
135        }));
136    };
137
138    // Parse parameters from { key: value, ... }
139    let params_str = params_str.trim_matches(|c| c == '{' || c == '}').trim();
140    let mut params_map = HashMap::new();
141
142    // Add type
143    params_map.insert("type".to_string(), Value::String("synth".to_string()));
144
145    // Handle waveform_or_plugin (what comes before the braces)
146    if !waveform_or_plugin.is_empty() {
147        // Check if it's a plugin reference (contains '.' → variable.property or plugin.author.name)
148        if waveform_or_plugin.contains('.') {
149            // Store plugin reference in internal field (will be resolved in interpreter)
150            params_map.insert("_plugin_ref".to_string(), Value::String(waveform_or_plugin.to_string()));
151            
152            // Also check for explicit plugin.author.name format
153            if waveform_or_plugin.starts_with("plugin.") {
154                let parts: Vec<&str> = waveform_or_plugin.split('.').collect();
155                if parts.len() >= 3 {
156                    params_map.insert("plugin_author".to_string(), Value::String(parts[1].to_string()));
157                    params_map.insert("plugin_name".to_string(), Value::String(parts[2].to_string()));
158                    if parts.len() >= 4 {
159                        params_map.insert("plugin_export".to_string(), Value::String(parts[3].to_string()));
160                    }
161                }
162            }
163        } else {
164            // It's a waveform string
165            params_map.insert("waveform".to_string(), Value::String(waveform_or_plugin.to_string()));
166        }
167    }
168
169    // Parse key: value pairs (support newlines by replacing them with commas)
170    if !params_str.is_empty() {
171        // First, remove inline comments (everything after //)
172        let mut cleaned_lines = Vec::new();
173        for line in params_str.lines() {
174            if let Some(comment_pos) = line.find("//") {
175                let clean_line = &line[..comment_pos];
176                if !clean_line.trim().is_empty() {
177                    cleaned_lines.push(clean_line);
178                }
179            } else if !line.trim().is_empty() {
180                cleaned_lines.push(line);
181            }
182        }
183
184        // Now join lines and split by comma and newline
185        let cleaned = cleaned_lines.join("\n");
186        let normalized = cleaned.replace('\n', ",").replace('\r', "");
187
188        for pair in normalized.split(',') {
189            let pair = pair.trim();
190            if pair.is_empty() {
191                continue;
192            }
193
194            let parts: Vec<&str> = pair.split(':').collect();
195            if parts.len() >= 2 {
196                let key = parts[0].trim().to_string();
197                // Join back in case value contains ':'
198                let value_part = parts[1..].join(":");
199                let value_str = value_part.trim().trim_matches(',').trim_matches('"');
200
201                // Parse arrays (for filters)
202                if value_str.starts_with('[') {
203                    if let Ok(array_val) = parse_array_value(value_str) {
204                        params_map.insert(key, array_val);
205                        continue;
206                    }
207                }
208
209                // Try to parse as number
210                if let Ok(num) = value_str.parse::<f32>() {
211                    params_map.insert(key, Value::Number(num));
212                } else {
213                    // Store as string (override waveform if specified in params)
214                    params_map.insert(key, Value::String(value_str.to_string()));
215                }
216            }
217        }
218    }
219
220    Ok(Value::Map(params_map))
221}
222
223/// Parse array value like [{ key: val }, ...]
224pub fn parse_array_value(input: &str) -> Result<Value> {
225    let input = input.trim().trim_matches(|c| c == '[' || c == ']').trim();
226    if input.is_empty() {
227        return Ok(Value::Array(Vec::new()));
228    }
229
230    // Check for range pattern: "start..end"
231    if input.contains("..") {
232        let parts: Vec<&str> = input.split("..").collect();
233        if parts.len() == 2 {
234            let start_str = parts[0].trim();
235            let end_str = parts[1].trim();
236
237            // Try to parse as numbers
238            if let (Ok(start), Ok(end)) = (start_str.parse::<f32>(), end_str.parse::<f32>()) {
239                return Ok(Value::Range {
240                    start: Box::new(Value::Number(start)),
241                    end: Box::new(Value::Number(end)),
242                });
243            }
244        }
245    }
246
247    let mut items = Vec::new();
248    let mut depth = 0;
249    let mut current = String::new();
250
251    for ch in input.chars() {
252        match ch {
253            '{' => {
254                depth += 1;
255                current.push(ch);
256            }
257            '}' => {
258                depth -= 1;
259                current.push(ch);
260
261                if depth == 0 && !current.trim().is_empty() {
262                    // Parse this object
263                    if let Ok(obj) = parse_map_value(&current) {
264                        items.push(obj);
265                    }
266                    current.clear();
267                }
268            }
269            ',' if depth == 0 => {
270                // Skip commas at array level
271                continue;
272            }
273            _ => {
274                current.push(ch);
275            }
276        }
277    }
278
279    Ok(Value::Array(items))
280}
281
282/// Parse map value like { key: val, key2: val2 }
283pub fn parse_map_value(input: &str) -> Result<Value> {
284    let input = input.trim().trim_matches(|c| c == '{' || c == '}').trim();
285    let mut map = HashMap::new();
286
287    for pair in input.split(',') {
288        let pair = pair.trim();
289        if pair.is_empty() {
290            continue;
291        }
292
293        let parts: Vec<&str> = pair.split(':').collect();
294        if parts.len() >= 2 {
295            let key = parts[0].trim().to_string();
296            // Join back in case value contains ':' (shouldn't happen but just in case)
297            let value_part = parts[1..].join(":");
298
299            // Remove inline comments (everything after //)
300            let value_clean = if let Some(comment_pos) = value_part.find("//") {
301                &value_part[..comment_pos]
302            } else {
303                &value_part
304            };
305
306            let value_str = value_clean.trim().trim_matches('"').trim_matches('\'');
307
308            // Try to parse as number
309            if let Ok(num) = value_str.parse::<f32>() {
310                map.insert(key, Value::Number(num));
311            } else {
312                map.insert(key, Value::String(value_str.to_string()));
313            }
314        }
315    }
316
317    Ok(Value::Map(map))
318}
319
320/// Parse a condition string into a Value (for if statements)
321/// Supports: var > value, var < value, var == value, var != value, var >= value, var <= value
322pub fn parse_condition(condition_str: &str) -> Result<Value> {
323    // Find the operator
324    let operators = vec![">=", "<=", "==", "!=", ">", "<"];
325    for op in operators {
326        if let Some(idx) = condition_str.find(op) {
327            let left = condition_str[..idx].trim();
328            let right = condition_str[idx + op.len()..].trim();
329
330            // Create a map representing the condition
331            let mut map = HashMap::new();
332            map.insert("operator".to_string(), Value::String(op.to_string()));
333            map.insert(
334                "left".to_string(),
335                if let Ok(num) = left.parse::<f32>() {
336                    Value::Number(num)
337                } else {
338                    Value::Identifier(left.to_string())
339                },
340            );
341            map.insert(
342                "right".to_string(),
343                if let Ok(num) = right.parse::<f32>() {
344                    Value::Number(num)
345                } else {
346                    Value::Identifier(right.to_string())
347                },
348            );
349
350            return Ok(Value::Map(map));
351        }
352    }
353
354    // No operator found, treat as boolean identifier
355    Ok(Value::Identifier(condition_str.to_string()))
356}