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 OR variable.export)
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 if input.contains('.') {
157                // Variable.property reference (e.g., acid.synth) - store for runtime resolution
158                map.insert("_plugin_ref".to_string(), Value::String(input.to_string()));
159            } else {
160                // Simple waveform like "sine" or "square"
161                map.insert("waveform".to_string(), Value::String(input.to_string()));
162            }
163
164            map
165        }));
166    };
167
168    // Parse parameters from { key: value, ... }
169    let params_str = params_str.trim_matches(|c| c == '{' || c == '}').trim();
170    let mut params_map = HashMap::new();
171
172    // Add type
173    params_map.insert("type".to_string(), Value::String("synth".to_string()));
174
175    // Handle waveform_or_plugin (what comes before the braces)
176    if !waveform_or_plugin.is_empty() {
177        // Check if it's a plugin reference (contains '.' → variable.property or plugin.author.name)
178        if waveform_or_plugin.contains('.') {
179            // Store plugin reference in internal field (will be resolved in interpreter)
180            params_map.insert(
181                "_plugin_ref".to_string(),
182                Value::String(waveform_or_plugin.to_string()),
183            );
184
185            // Also check for explicit plugin.author.name format
186            if waveform_or_plugin.starts_with("plugin.") {
187                let parts: Vec<&str> = waveform_or_plugin.split('.').collect();
188                if parts.len() >= 3 {
189                    params_map.insert(
190                        "plugin_author".to_string(),
191                        Value::String(parts[1].to_string()),
192                    );
193                    params_map.insert(
194                        "plugin_name".to_string(),
195                        Value::String(parts[2].to_string()),
196                    );
197                    if parts.len() >= 4 {
198                        params_map.insert(
199                            "plugin_export".to_string(),
200                            Value::String(parts[3].to_string()),
201                        );
202                    }
203                }
204            }
205        } else {
206            // It's a waveform string
207            params_map.insert(
208                "waveform".to_string(),
209                Value::String(waveform_or_plugin.to_string()),
210            );
211        }
212    }
213
214    // Parse key: value pairs (support newlines by replacing them with commas)
215    if !params_str.is_empty() {
216        // First, remove inline comments (everything after //)
217        let mut cleaned_lines = Vec::new();
218        for line in params_str.lines() {
219            if let Some(comment_pos) = line.find("//") {
220                let clean_line = &line[..comment_pos];
221                if !clean_line.trim().is_empty() {
222                    cleaned_lines.push(clean_line);
223                }
224            } else if !line.trim().is_empty() {
225                cleaned_lines.push(line);
226            }
227        }
228
229        // Now join lines and split by comma and newline
230        let cleaned = cleaned_lines.join("\n");
231        let normalized = cleaned.replace('\n', ",").replace('\r', "");
232
233        for pair in normalized.split(',') {
234            let pair = pair.trim();
235            if pair.is_empty() {
236                continue;
237            }
238
239            let parts: Vec<&str> = pair.split(':').collect();
240            if parts.len() >= 2 {
241                let key = parts[0].trim().to_string();
242                // Join back in case value contains ':'
243                let value_part = parts[1..].join(":");
244                let value_str = value_part.trim().trim_matches(',').trim_matches('"');
245
246                // Parse arrays (for filters)
247                if value_str.starts_with('[') {
248                    if let Ok(array_val) = parse_array_value(value_str) {
249                        params_map.insert(key, array_val);
250                        continue;
251                    }
252                }
253
254                // Try to parse as number
255                if let Ok(num) = value_str.parse::<f32>() {
256                    params_map.insert(key, Value::Number(num));
257                } else {
258                    // Store as string (override waveform if specified in params)
259                    params_map.insert(key, Value::String(value_str.to_string()));
260                }
261            }
262        }
263    }
264
265    Ok(Value::Map(params_map))
266}
267
268/// Parse array value like [{ key: val }, ...]
269pub fn parse_array_value(input: &str) -> Result<Value> {
270    let input = input.trim().trim_matches(|c| c == '[' || c == ']').trim();
271    if input.is_empty() {
272        return Ok(Value::Array(Vec::new()));
273    }
274
275    // Check for range pattern: "start..end"
276    if input.contains("..") {
277        let parts: Vec<&str> = input.split("..").collect();
278        if parts.len() == 2 {
279            let start_str = parts[0].trim();
280            let end_str = parts[1].trim();
281
282            // Try to parse as numbers
283            if let (Ok(start), Ok(end)) = (start_str.parse::<f32>(), end_str.parse::<f32>()) {
284                return Ok(Value::Range {
285                    start: Box::new(Value::Number(start)),
286                    end: Box::new(Value::Number(end)),
287                });
288            }
289        }
290    }
291
292    let mut items = Vec::new();
293    let mut depth = 0;
294    let mut current = String::new();
295
296    for ch in input.chars() {
297        match ch {
298            '{' => {
299                depth += 1;
300                current.push(ch);
301            }
302            '}' => {
303                depth -= 1;
304                current.push(ch);
305
306                if depth == 0 && !current.trim().is_empty() {
307                    // Parse this object
308                    if let Ok(obj) = parse_map_value(&current) {
309                        items.push(obj);
310                    }
311                    current.clear();
312                }
313            }
314            ',' if depth == 0 => {
315                // End of a top-level array element (not inside an object).
316                let token = current.trim();
317                if !token.is_empty() {
318                    // Parse simple token (number, identifier, string, nested array/map)
319                    if let Ok(val) = parse_single_arg(token) {
320                        // If the parsed value is a Map, keep it. Otherwise, wrap into a Map
321                        // with `index` and `value` keys so arrays of simple tokens become
322                        // objects supporting property access (index, value).
323                        if let Value::Map(_) = val {
324                            items.push(val);
325                        } else {
326                            let mut map = HashMap::new();
327                            let idx = items.len();
328                            map.insert("index".to_string(), Value::Number(idx as f32));
329                            map.insert("value".to_string(), val);
330                            items.push(Value::Map(map));
331                        }
332                    }
333                }
334                current.clear();
335                continue;
336            }
337            _ => {
338                current.push(ch);
339            }
340        }
341    }
342
343    // If there is a trailing token after the loop, parse and push it
344    if !current.trim().is_empty() {
345        let token = current.trim();
346        // If it's an object it should have been handled above; otherwise parse as single arg
347        if !token.is_empty() {
348            if let Ok(val) = parse_single_arg(token) {
349                items.push(val);
350            }
351        }
352    }
353
354    Ok(Value::Array(items))
355}
356
357/// Parse map value like { key: val, key2: val2 }
358pub fn parse_map_value(input: &str) -> Result<Value> {
359    let input = input.trim().trim_matches(|c| c == '{' || c == '}').trim();
360    let mut map = HashMap::new();
361
362    for pair in input.split(',') {
363        let pair = pair.trim();
364        if pair.is_empty() {
365            continue;
366        }
367
368        let parts: Vec<&str> = pair.split(':').collect();
369        if parts.len() >= 2 {
370            let key = parts[0].trim().to_string();
371            // Join back in case value contains ':' (shouldn't happen but just in case)
372            let value_part = parts[1..].join(":");
373
374            // Remove inline comments (everything after //)
375            let value_clean = if let Some(comment_pos) = value_part.find("//") {
376                &value_part[..comment_pos]
377            } else {
378                &value_part
379            };
380
381            let value_str = value_clean.trim().trim_matches('"').trim_matches('\'');
382
383            // Try to parse as number
384            if let Ok(num) = value_str.parse::<f32>() {
385                map.insert(key, Value::Number(num));
386            } else {
387                map.insert(key, Value::String(value_str.to_string()));
388            }
389        }
390    }
391
392    Ok(Value::Map(map))
393}
394
395/// Find a word boundary in a string (for keyword operators like "and", "or")
396/// Returns the index where the word starts, or None if not found
397fn find_word_boundary(text: &str, keyword: &str) -> Option<usize> {
398    // Find rightmost occurrence (lowest precedence)
399    text.rfind(keyword).and_then(|idx| {
400        // Check that it's a word boundary (not part of an identifier)
401        let before_ok =
402            idx == 0 || !text[..idx].ends_with(|c: char| c.is_alphanumeric() || c == '_');
403        let after_idx = idx + keyword.len();
404        let after_ok = after_idx >= text.len()
405            || !text[after_idx..].starts_with(|c: char| c.is_alphanumeric() || c == '_');
406
407        if before_ok && after_ok {
408            Some(idx)
409        } else {
410            None
411        }
412    })
413}
414
415/// Parse a condition string into a Value (for if statements)
416/// Supports: var > value, var < value, var == value, var != value, var >= value, var <= value
417/// Also supports logical operators: &&/and, ||/or, !/not
418pub fn parse_condition(condition_str: &str) -> Result<Value> {
419    let condition_str = condition_str.trim();
420
421    // Handle OR operator (lowest precedence) - support both "||" and "or"
422    if let Some(idx) = condition_str.rfind("||") {
423        let left = parse_condition(&condition_str[..idx].trim())?;
424        let right = parse_condition(&condition_str[idx + 2..].trim())?;
425
426        let mut map = HashMap::new();
427        map.insert("operator".to_string(), Value::String("||".to_string()));
428        map.insert("left".to_string(), left);
429        map.insert("right".to_string(), right);
430        return Ok(Value::Map(map));
431    }
432
433    // Handle "or" keyword operator (support word-based OR)
434    if let Some(idx) = find_word_boundary(condition_str, " or ") {
435        let left = parse_condition(&condition_str[..idx].trim())?;
436        let right = parse_condition(&condition_str[idx + 4..].trim())?;
437
438        let mut map = HashMap::new();
439        map.insert("operator".to_string(), Value::String("||".to_string()));
440        map.insert("left".to_string(), left);
441        map.insert("right".to_string(), right);
442        return Ok(Value::Map(map));
443    }
444
445    // Handle AND operator (higher precedence than OR) - support both "&&" and "and"
446    if let Some(idx) = condition_str.rfind("&&") {
447        let left = parse_condition(&condition_str[..idx].trim())?;
448        let right = parse_condition(&condition_str[idx + 2..].trim())?;
449
450        let mut map = HashMap::new();
451        map.insert("operator".to_string(), Value::String("&&".to_string()));
452        map.insert("left".to_string(), left);
453        map.insert("right".to_string(), right);
454        return Ok(Value::Map(map));
455    }
456
457    // Handle "and" keyword operator (support word-based AND)
458    if let Some(idx) = find_word_boundary(condition_str, " and ") {
459        let left = parse_condition(&condition_str[..idx].trim())?;
460        let right = parse_condition(&condition_str[idx + 5..].trim())?;
461
462        let mut map = HashMap::new();
463        map.insert("operator".to_string(), Value::String("&&".to_string()));
464        map.insert("left".to_string(), left);
465        map.insert("right".to_string(), right);
466        return Ok(Value::Map(map));
467    }
468
469    // Handle NOT operator (highest precedence) - support "!"
470    if condition_str.starts_with('!') {
471        let inner = parse_condition(&condition_str[1..].trim())?;
472
473        let mut map = HashMap::new();
474        map.insert("operator".to_string(), Value::String("!".to_string()));
475        map.insert("operand".to_string(), inner);
476        return Ok(Value::Map(map));
477    }
478
479    // Handle "not" keyword operator (support word-based NOT)
480    if condition_str.starts_with("not ") {
481        let inner = parse_condition(&condition_str[4..].trim())?;
482
483        let mut map = HashMap::new();
484        map.insert("operator".to_string(), Value::String("!".to_string()));
485        map.insert("operand".to_string(), inner);
486        return Ok(Value::Map(map));
487    }
488
489    // Handle comparison operators
490    let operators = vec![">=", "<=", "==", "!=", ">", "<"];
491    for op in operators {
492        if let Some(idx) = condition_str.find(op) {
493            let left = condition_str[..idx].trim();
494            let right = condition_str[idx + op.len()..].trim();
495
496            // Create a map representing the condition
497            let mut map = HashMap::new();
498            map.insert("operator".to_string(), Value::String(op.to_string()));
499            map.insert(
500                "left".to_string(),
501                if let Ok(num) = left.parse::<f32>() {
502                    Value::Number(num)
503                } else {
504                    Value::Identifier(left.to_string())
505                },
506            );
507            map.insert(
508                "right".to_string(),
509                if let Ok(num) = right.parse::<f32>() {
510                    Value::Number(num)
511                } else {
512                    Value::Identifier(right.to_string())
513                },
514            );
515
516            return Ok(Value::Map(map));
517        }
518    }
519
520    // No operator found, treat as boolean identifier
521    Ok(Value::Identifier(condition_str.to_string()))
522}