Skip to main content

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