devalang_wasm/language/syntax/parser/driver/statements/
advanced.rs

1use super::super::helpers::{parse_function_args, parse_map_value};
2use crate::language::syntax::ast::{Statement, StatementKind, Value};
3/// Advanced statement parsing: ArrowCall, Assign, Automate, Bind
4use anyhow::{Result, anyhow};
5use std::collections::HashMap;
6
7/// Parse an arrow call: target -> method(args) -> method2(args2)
8/// Supports chaining multiple calls
9pub fn parse_arrow_call(line: &str, line_number: usize) -> Result<Statement> {
10    // Split by "->" to get chain of calls
11    let parts: Vec<&str> = line.split("->").map(|s| s.trim()).collect();
12
13    if parts.len() < 2 {
14        return Err(anyhow!("Arrow call requires at least one '->' operator"));
15    }
16
17    // First part is the target
18    let target = parts[0].to_string();
19
20    // Parse method calls
21    let mut calls = Vec::new();
22
23    for method_call in &parts[1..] {
24        // Parse method(args) or just method
25        if let Some(paren_idx) = method_call.find('(') {
26            let mut method_name = method_call[..paren_idx].trim().to_string();
27            // Accept both vel(...) and velocity(...)
28            if method_name == "vel" {
29                method_name = "velocity".to_string();
30            }
31            let args_str = &method_call[paren_idx + 1..];
32
33            // Find matching closing paren
34            let close_paren = args_str
35                .rfind(')')
36                .ok_or_else(|| anyhow!("Missing closing parenthesis in arrow call"))?;
37
38            let args_str = &args_str[..close_paren];
39
40            // Parse arguments
41            let args = if args_str.trim().is_empty() {
42                Vec::new()
43            } else {
44                parse_function_args(args_str)?
45            };
46
47            calls.push((method_name.to_string(), args));
48        } else {
49            // Method without args
50            calls.push((method_call.trim().to_string(), Vec::new()));
51        }
52    }
53
54    // For now, we'll store all calls as separate ArrowCall statements
55    // or we can store them in a chain structure
56    // Let's store the first call and chain the rest
57
58    if calls.is_empty() {
59        return Err(anyhow!("No method calls found in arrow call"));
60    }
61
62    let (method, args) = calls[0].clone();
63
64    // Store chain in value for later processing
65    let mut chain_value = HashMap::new();
66    chain_value.insert("target".to_string(), Value::String(target.clone()));
67    chain_value.insert("method".to_string(), Value::String(method.clone()));
68    chain_value.insert("args".to_string(), Value::Array(args.clone()));
69
70    // Add remaining calls to chain
71    if calls.len() > 1 {
72        let chain_calls: Vec<Value> = calls[1..]
73            .iter()
74            .map(|(m, a)| {
75                let mut call_map = HashMap::new();
76                call_map.insert("method".to_string(), Value::String(m.clone()));
77                call_map.insert("args".to_string(), Value::Array(a.clone()));
78                Value::Map(call_map)
79            })
80            .collect();
81
82        chain_value.insert("chain".to_string(), Value::Array(chain_calls));
83    }
84
85    Ok(Statement::new(
86        StatementKind::ArrowCall {
87            target,
88            method,
89            args,
90        },
91        Value::Map(chain_value),
92        0,
93        line_number,
94        1,
95    ))
96}
97
98/// Parse property assignment: target.property = value
99pub fn parse_assign(line: &str, line_number: usize) -> Result<Statement> {
100    let assign_parts: Vec<&str> = line.splitn(2, '=').collect();
101    if assign_parts.len() != 2 {
102        return Err(anyhow!("Invalid assignment syntax"));
103    }
104
105    let left = assign_parts[0].trim();
106    let right = assign_parts[1].trim();
107
108    // Split left into target.property
109    let prop_parts: Vec<&str> = left.splitn(2, '.').collect();
110    if prop_parts.len() != 2 {
111        return Err(anyhow!("Assignment requires target.property syntax"));
112    }
113
114    let target = prop_parts[0].trim().to_string();
115    let property = prop_parts[1].trim().to_string();
116
117    // Parse value
118    let value = if let Ok(num) = right.parse::<f32>() {
119        Value::Number(num)
120    } else if right.starts_with('"') && right.ends_with('"') {
121        Value::String(right.trim_matches('"').to_string())
122    } else {
123        Value::Identifier(right.to_string())
124    };
125
126    Ok(Statement::new(
127        StatementKind::Assign { target, property },
128        value,
129        0,
130        line_number,
131        1,
132    ))
133}
134
135/// Parse bind statement: bind source -> target { options }
136pub fn parse_bind(line: &str, line_number: usize) -> Result<Statement> {
137    // Supported forms:
138    //   bind <left> -> <right>
139    //   bind <left> -> <right> with { ... }
140    //   bind <left> with { ... } -> <right>
141    // We'll normalize to source/target and put the options (if any) into the statement value.
142
143    // Remove leading 'bind' and split on '->'
144    let after_bind = line
145        .trim()
146        .strip_prefix("bind")
147        .ok_or_else(|| anyhow!("bind parsing error"))?
148        .trim();
149
150    let arrow_parts: Vec<&str> = after_bind.splitn(2, "->").collect();
151    if arrow_parts.len() != 2 {
152        return Err(anyhow!("bind requires source -> target syntax"));
153    }
154
155    // Left side may contain 'with { ... }'
156    let left_raw = arrow_parts[0].trim();
157    let right_raw = arrow_parts[1].trim();
158
159    // Helper to extract optional 'with { ... }' from a side
160    fn extract_with(side: &str) -> Result<(String, Option<Value>)> {
161        let s = side.trim();
162        // Look for 'with {' pattern
163        if let Some(with_pos) = s.find("with") {
164            let before = s[..with_pos].trim().to_string();
165            let after = s[with_pos + "with".len()..].trim();
166            if after.starts_with('{') {
167                if let Some(close_brace) = after.rfind('}') {
168                    let options_str = &after[..close_brace + 1];
169                    let options = parse_map_value(options_str)?;
170                    return Ok((before, Some(options)));
171                } else {
172                    return Err(anyhow!("unclosed brace in bind options"));
173                }
174            }
175        }
176        // No 'with' found
177        Ok((s.to_string(), None))
178    }
179
180    let (source_candidate, left_opts) = extract_with(left_raw)?;
181    let (target_candidate, right_opts) = extract_with(right_raw)?;
182
183    // Merge options, favouring explicit right-side options (target) over left-side
184    let merged_options = match (left_opts, right_opts) {
185        (Some(mut l), Some(r)) => {
186            // merge maps: r overrides l
187            if let (Value::Map(lm), Value::Map(rm)) = (&mut l, &r) {
188                for (k, v) in rm.iter() {
189                    lm.insert(k.clone(), v.clone());
190                }
191                Some(l)
192            } else {
193                Some(r)
194            }
195        }
196        (Some(l), None) => Some(l),
197        (None, Some(r)) => Some(r),
198        (None, None) => None,
199    };
200
201    let source = source_candidate;
202    let target = target_candidate;
203
204    Ok(Statement::new(
205        StatementKind::Bind { source, target },
206        merged_options.unwrap_or(Value::Null),
207        0,
208        line_number,
209        1,
210    ))
211}