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    // Function call expression: name(arg1, arg2)
88    if let Some(open_paren) = arg.find('(') {
89        if arg.ends_with(')') {
90            let name = arg[..open_paren].trim().to_string();
91            let args_str = &arg[open_paren + 1..arg.len() - 1];
92            let parsed_args = if args_str.trim().is_empty() {
93                Vec::new()
94            } else {
95                parse_function_args(args_str)?
96            };
97            return Ok(Value::Call {
98                name,
99                args: parsed_args,
100            });
101        }
102    }
103
104    // Boolean
105    match arg.to_lowercase().as_str() {
106        "true" => return Ok(Value::Boolean(true)),
107        "false" => return Ok(Value::Boolean(false)),
108        _ => {}
109    }
110
111    // Default to identifier
112    Ok(Value::Identifier(arg.to_string()))
113}
114
115/// Parse synth definition: synth waveform { params } OR synth plugin.<name> { params }
116/// Returns a Map with type="synth", waveform/plugin info, and parameters
117///
118/// Supported syntaxes:
119/// - synth "sine" { attack: 0.1 }
120/// - synth plugin.acid.synth { waveform: "sine" }
121/// - synth { waveform: "sine", attack: 0.1 }  // waveform in params
122pub fn parse_synth_definition(input: &str) -> Result<Value> {
123    // Remove "synth " prefix
124    let input = input.trim_start_matches("synth ").trim();
125
126    // Check if we have braces
127    let (waveform_or_plugin, params_str) = if let Some(brace_idx) = input.find('{') {
128        let before_brace = input[..brace_idx].trim();
129        let params = &input[brace_idx..];
130        (before_brace, params)
131    } else {
132        // No parameters, just waveform or plugin reference
133        return Ok(Value::Map({
134            let mut map = HashMap::new();
135            map.insert("type".to_string(), Value::String("synth".to_string()));
136
137            // Check if it's a plugin reference (plugin.author.name)
138            if input.starts_with("plugin.") {
139                let parts: Vec<&str> = input.split('.').collect();
140                if parts.len() >= 3 {
141                    map.insert(
142                        "plugin_author".to_string(),
143                        Value::String(parts[1].to_string()),
144                    );
145                    map.insert(
146                        "plugin_name".to_string(),
147                        Value::String(parts[2].to_string()),
148                    );
149                    if parts.len() >= 4 {
150                        map.insert(
151                            "plugin_export".to_string(),
152                            Value::String(parts[3].to_string()),
153                        );
154                    }
155                }
156            } else {
157                map.insert("waveform".to_string(), Value::String(input.to_string()));
158            }
159
160            map
161        }));
162    };
163
164    // Parse parameters from { key: value, ... }
165    let params_str = params_str.trim_matches(|c| c == '{' || c == '}').trim();
166    let mut params_map = HashMap::new();
167
168    // Add type
169    params_map.insert("type".to_string(), Value::String("synth".to_string()));
170
171    // Handle waveform_or_plugin (what comes before the braces)
172    if !waveform_or_plugin.is_empty() {
173        // Check if it's a plugin reference (contains '.' → variable.property or plugin.author.name)
174        if waveform_or_plugin.contains('.') {
175            // Store plugin reference in internal field (will be resolved in interpreter)
176            params_map.insert(
177                "_plugin_ref".to_string(),
178                Value::String(waveform_or_plugin.to_string()),
179            );
180
181            // Also check for explicit plugin.author.name format
182            if waveform_or_plugin.starts_with("plugin.") {
183                let parts: Vec<&str> = waveform_or_plugin.split('.').collect();
184                if parts.len() >= 3 {
185                    params_map.insert(
186                        "plugin_author".to_string(),
187                        Value::String(parts[1].to_string()),
188                    );
189                    params_map.insert(
190                        "plugin_name".to_string(),
191                        Value::String(parts[2].to_string()),
192                    );
193                    if parts.len() >= 4 {
194                        params_map.insert(
195                            "plugin_export".to_string(),
196                            Value::String(parts[3].to_string()),
197                        );
198                    }
199                }
200            }
201        } else {
202            // It's a waveform string
203            params_map.insert(
204                "waveform".to_string(),
205                Value::String(waveform_or_plugin.to_string()),
206            );
207        }
208    }
209
210    // Parse key: value pairs (support newlines by replacing them with commas)
211    if !params_str.is_empty() {
212        // First, remove inline comments (everything after //)
213        let mut cleaned_lines = Vec::new();
214        for line in params_str.lines() {
215            if let Some(comment_pos) = line.find("//") {
216                let clean_line = &line[..comment_pos];
217                if !clean_line.trim().is_empty() {
218                    cleaned_lines.push(clean_line);
219                }
220            } else if !line.trim().is_empty() {
221                cleaned_lines.push(line);
222            }
223        }
224
225        // Now join lines and split by comma and newline
226        let cleaned = cleaned_lines.join("\n");
227        let normalized = cleaned.replace('\n', ",").replace('\r', "");
228
229        for pair in normalized.split(',') {
230            let pair = pair.trim();
231            if pair.is_empty() {
232                continue;
233            }
234
235            let parts: Vec<&str> = pair.split(':').collect();
236            if parts.len() >= 2 {
237                let key = parts[0].trim().to_string();
238                // Join back in case value contains ':'
239                let value_part = parts[1..].join(":");
240                let value_str = value_part.trim().trim_matches(',').trim_matches('"');
241
242                // Parse arrays (for filters)
243                if value_str.starts_with('[') {
244                    if let Ok(array_val) = parse_array_value(value_str) {
245                        params_map.insert(key, array_val);
246                        continue;
247                    }
248                }
249
250                // Try to parse as number
251                if let Ok(num) = value_str.parse::<f32>() {
252                    params_map.insert(key, Value::Number(num));
253                } else {
254                    // Store as string (override waveform if specified in params)
255                    params_map.insert(key, Value::String(value_str.to_string()));
256                }
257            }
258        }
259    }
260
261    Ok(Value::Map(params_map))
262}
263
264/// Parse array value like [{ key: val }, ...]
265pub fn parse_array_value(input: &str) -> Result<Value> {
266    let input = input.trim().trim_matches(|c| c == '[' || c == ']').trim();
267    if input.is_empty() {
268        return Ok(Value::Array(Vec::new()));
269    }
270
271    // Check for range pattern: "start..end"
272    if input.contains("..") {
273        let parts: Vec<&str> = input.split("..").collect();
274        if parts.len() == 2 {
275            let start_str = parts[0].trim();
276            let end_str = parts[1].trim();
277
278            // Try to parse as numbers
279            if let (Ok(start), Ok(end)) = (start_str.parse::<f32>(), end_str.parse::<f32>()) {
280                return Ok(Value::Range {
281                    start: Box::new(Value::Number(start)),
282                    end: Box::new(Value::Number(end)),
283                });
284            }
285        }
286    }
287
288    let mut items = Vec::new();
289    let mut depth = 0;
290    let mut current = String::new();
291
292    for ch in input.chars() {
293        match ch {
294            '{' => {
295                depth += 1;
296                current.push(ch);
297            }
298            '}' => {
299                depth -= 1;
300                current.push(ch);
301
302                if depth == 0 && !current.trim().is_empty() {
303                    // Parse this object
304                    if let Ok(obj) = parse_map_value(&current) {
305                        items.push(obj);
306                    }
307                    current.clear();
308                }
309            }
310            ',' if depth == 0 => {
311                // End of a top-level array element (not inside an object).
312                let token = current.trim();
313                if !token.is_empty() {
314                    // Parse simple token (number, identifier, string, nested array/map)
315                    if let Ok(val) = parse_single_arg(token) {
316                        // If the parsed value is a Map, keep it. Otherwise, wrap into a Map
317                        // with `index` and `value` keys so arrays of simple tokens become
318                        // objects supporting property access (index, value).
319                        if let Value::Map(_) = val {
320                            items.push(val);
321                        } else {
322                            let mut map = HashMap::new();
323                            let idx = items.len();
324                            map.insert("index".to_string(), Value::Number(idx as f32));
325                            map.insert("value".to_string(), val);
326                            items.push(Value::Map(map));
327                        }
328                    }
329                }
330                current.clear();
331                continue;
332            }
333            _ => {
334                current.push(ch);
335            }
336        }
337    }
338
339    // If there is a trailing token after the loop, parse and push it
340    if !current.trim().is_empty() {
341        let token = current.trim();
342        // If it's an object it should have been handled above; otherwise parse as single arg
343        if !token.is_empty() {
344            if let Ok(val) = parse_single_arg(token) {
345                items.push(val);
346            }
347        }
348    }
349
350    Ok(Value::Array(items))
351}
352
353/// Parse map value like { key: val, key2: val2 }
354pub fn parse_map_value(input: &str) -> Result<Value> {
355    let input = input.trim().trim_matches(|c| c == '{' || c == '}').trim();
356    let mut map = HashMap::new();
357
358    for pair in input.split(',') {
359        let pair = pair.trim();
360        if pair.is_empty() {
361            continue;
362        }
363
364        let parts: Vec<&str> = pair.split(':').collect();
365        if parts.len() >= 2 {
366            let key = parts[0].trim().to_string();
367            // Join back in case value contains ':' (shouldn't happen but just in case)
368            let value_part = parts[1..].join(":");
369
370            // Remove inline comments (everything after //)
371            let value_clean = if let Some(comment_pos) = value_part.find("//") {
372                &value_part[..comment_pos]
373            } else {
374                &value_part
375            };
376
377            let value_str = value_clean.trim().trim_matches('"').trim_matches('\'');
378
379            // Try to parse as number
380            if let Ok(num) = value_str.parse::<f32>() {
381                map.insert(key, Value::Number(num));
382            } else {
383                map.insert(key, Value::String(value_str.to_string()));
384            }
385        }
386    }
387
388    Ok(Value::Map(map))
389}
390
391/// Parse a condition string into a Value (for if statements)
392/// Supports: var > value, var < value, var == value, var != value, var >= value, var <= value
393pub fn parse_condition(condition_str: &str) -> Result<Value> {
394    // Find the operator
395    let operators = vec![">=", "<=", "==", "!=", ">", "<"];
396    for op in operators {
397        if let Some(idx) = condition_str.find(op) {
398            let left = condition_str[..idx].trim();
399            let right = condition_str[idx + op.len()..].trim();
400
401            // Create a map representing the condition
402            let mut map = HashMap::new();
403            map.insert("operator".to_string(), Value::String(op.to_string()));
404            map.insert(
405                "left".to_string(),
406                if let Ok(num) = left.parse::<f32>() {
407                    Value::Number(num)
408                } else {
409                    Value::Identifier(left.to_string())
410                },
411            );
412            map.insert(
413                "right".to_string(),
414                if let Ok(num) = right.parse::<f32>() {
415                    Value::Number(num)
416                } else {
417                    Value::Identifier(right.to_string())
418                },
419            );
420
421            return Ok(Value::Map(map));
422        }
423    }
424
425    // No operator found, treat as boolean identifier
426    Ok(Value::Identifier(condition_str.to_string()))
427}