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(
125                        "plugin_author".to_string(),
126                        Value::String(parts[1].to_string()),
127                    );
128                    map.insert(
129                        "plugin_name".to_string(),
130                        Value::String(parts[2].to_string()),
131                    );
132                    if parts.len() >= 4 {
133                        map.insert(
134                            "plugin_export".to_string(),
135                            Value::String(parts[3].to_string()),
136                        );
137                    }
138                }
139            } else {
140                map.insert("waveform".to_string(), Value::String(input.to_string()));
141            }
142
143            map
144        }));
145    };
146
147    // Parse parameters from { key: value, ... }
148    let params_str = params_str.trim_matches(|c| c == '{' || c == '}').trim();
149    let mut params_map = HashMap::new();
150
151    // Add type
152    params_map.insert("type".to_string(), Value::String("synth".to_string()));
153
154    // Handle waveform_or_plugin (what comes before the braces)
155    if !waveform_or_plugin.is_empty() {
156        // Check if it's a plugin reference (contains '.' → variable.property or plugin.author.name)
157        if waveform_or_plugin.contains('.') {
158            // Store plugin reference in internal field (will be resolved in interpreter)
159            params_map.insert(
160                "_plugin_ref".to_string(),
161                Value::String(waveform_or_plugin.to_string()),
162            );
163
164            // Also check for explicit plugin.author.name format
165            if waveform_or_plugin.starts_with("plugin.") {
166                let parts: Vec<&str> = waveform_or_plugin.split('.').collect();
167                if parts.len() >= 3 {
168                    params_map.insert(
169                        "plugin_author".to_string(),
170                        Value::String(parts[1].to_string()),
171                    );
172                    params_map.insert(
173                        "plugin_name".to_string(),
174                        Value::String(parts[2].to_string()),
175                    );
176                    if parts.len() >= 4 {
177                        params_map.insert(
178                            "plugin_export".to_string(),
179                            Value::String(parts[3].to_string()),
180                        );
181                    }
182                }
183            }
184        } else {
185            // It's a waveform string
186            params_map.insert(
187                "waveform".to_string(),
188                Value::String(waveform_or_plugin.to_string()),
189            );
190        }
191    }
192
193    // Parse key: value pairs (support newlines by replacing them with commas)
194    if !params_str.is_empty() {
195        // First, remove inline comments (everything after //)
196        let mut cleaned_lines = Vec::new();
197        for line in params_str.lines() {
198            if let Some(comment_pos) = line.find("//") {
199                let clean_line = &line[..comment_pos];
200                if !clean_line.trim().is_empty() {
201                    cleaned_lines.push(clean_line);
202                }
203            } else if !line.trim().is_empty() {
204                cleaned_lines.push(line);
205            }
206        }
207
208        // Now join lines and split by comma and newline
209        let cleaned = cleaned_lines.join("\n");
210        let normalized = cleaned.replace('\n', ",").replace('\r', "");
211
212        for pair in normalized.split(',') {
213            let pair = pair.trim();
214            if pair.is_empty() {
215                continue;
216            }
217
218            let parts: Vec<&str> = pair.split(':').collect();
219            if parts.len() >= 2 {
220                let key = parts[0].trim().to_string();
221                // Join back in case value contains ':'
222                let value_part = parts[1..].join(":");
223                let value_str = value_part.trim().trim_matches(',').trim_matches('"');
224
225                // Parse arrays (for filters)
226                if value_str.starts_with('[') {
227                    if let Ok(array_val) = parse_array_value(value_str) {
228                        params_map.insert(key, array_val);
229                        continue;
230                    }
231                }
232
233                // Try to parse as number
234                if let Ok(num) = value_str.parse::<f32>() {
235                    params_map.insert(key, Value::Number(num));
236                } else {
237                    // Store as string (override waveform if specified in params)
238                    params_map.insert(key, Value::String(value_str.to_string()));
239                }
240            }
241        }
242    }
243
244    Ok(Value::Map(params_map))
245}
246
247/// Parse array value like [{ key: val }, ...]
248pub fn parse_array_value(input: &str) -> Result<Value> {
249    let input = input.trim().trim_matches(|c| c == '[' || c == ']').trim();
250    if input.is_empty() {
251        return Ok(Value::Array(Vec::new()));
252    }
253
254    // Check for range pattern: "start..end"
255    if input.contains("..") {
256        let parts: Vec<&str> = input.split("..").collect();
257        if parts.len() == 2 {
258            let start_str = parts[0].trim();
259            let end_str = parts[1].trim();
260
261            // Try to parse as numbers
262            if let (Ok(start), Ok(end)) = (start_str.parse::<f32>(), end_str.parse::<f32>()) {
263                return Ok(Value::Range {
264                    start: Box::new(Value::Number(start)),
265                    end: Box::new(Value::Number(end)),
266                });
267            }
268        }
269    }
270
271    let mut items = Vec::new();
272    let mut depth = 0;
273    let mut current = String::new();
274
275    for ch in input.chars() {
276        match ch {
277            '{' => {
278                depth += 1;
279                current.push(ch);
280            }
281            '}' => {
282                depth -= 1;
283                current.push(ch);
284
285                if depth == 0 && !current.trim().is_empty() {
286                    // Parse this object
287                    if let Ok(obj) = parse_map_value(&current) {
288                        items.push(obj);
289                    }
290                    current.clear();
291                }
292            }
293            ',' if depth == 0 => {
294                // Skip commas at array level
295                continue;
296            }
297            _ => {
298                current.push(ch);
299            }
300        }
301    }
302
303    Ok(Value::Array(items))
304}
305
306/// Parse map value like { key: val, key2: val2 }
307pub fn parse_map_value(input: &str) -> Result<Value> {
308    let input = input.trim().trim_matches(|c| c == '{' || c == '}').trim();
309    let mut map = HashMap::new();
310
311    for pair in input.split(',') {
312        let pair = pair.trim();
313        if pair.is_empty() {
314            continue;
315        }
316
317        let parts: Vec<&str> = pair.split(':').collect();
318        if parts.len() >= 2 {
319            let key = parts[0].trim().to_string();
320            // Join back in case value contains ':' (shouldn't happen but just in case)
321            let value_part = parts[1..].join(":");
322
323            // Remove inline comments (everything after //)
324            let value_clean = if let Some(comment_pos) = value_part.find("//") {
325                &value_part[..comment_pos]
326            } else {
327                &value_part
328            };
329
330            let value_str = value_clean.trim().trim_matches('"').trim_matches('\'');
331
332            // Try to parse as number
333            if let Ok(num) = value_str.parse::<f32>() {
334                map.insert(key, Value::Number(num));
335            } else {
336                map.insert(key, Value::String(value_str.to_string()));
337            }
338        }
339    }
340
341    Ok(Value::Map(map))
342}
343
344/// Parse a condition string into a Value (for if statements)
345/// Supports: var > value, var < value, var == value, var != value, var >= value, var <= value
346pub fn parse_condition(condition_str: &str) -> Result<Value> {
347    // Find the operator
348    let operators = vec![">=", "<=", "==", "!=", ">", "<"];
349    for op in operators {
350        if let Some(idx) = condition_str.find(op) {
351            let left = condition_str[..idx].trim();
352            let right = condition_str[idx + op.len()..].trim();
353
354            // Create a map representing the condition
355            let mut map = HashMap::new();
356            map.insert("operator".to_string(), Value::String(op.to_string()));
357            map.insert(
358                "left".to_string(),
359                if let Ok(num) = left.parse::<f32>() {
360                    Value::Number(num)
361                } else {
362                    Value::Identifier(left.to_string())
363                },
364            );
365            map.insert(
366                "right".to_string(),
367                if let Ok(num) = right.parse::<f32>() {
368                    Value::Number(num)
369                } else {
370                    Value::Identifier(right.to_string())
371                },
372            );
373
374            return Ok(Value::Map(map));
375        }
376    }
377
378    // No operator found, treat as boolean identifier
379    Ok(Value::Identifier(condition_str.to_string()))
380}