devalang_core/core/parser/handler/identifier/
automate.rs

1use crate::core::{
2    lexer::token::{Token, TokenKind},
3    parser::{
4        driver::parser::Parser,
5        statement::{Statement, StatementKind},
6    },
7    store::global::GlobalStore,
8};
9use devalang_types::Value;
10use std::collections::HashMap;
11
12pub fn parse_automate_token(
13    parser: &mut Parser,
14    current_token: Token,
15    _global_store: &mut GlobalStore,
16) -> Statement {
17    parser.advance(); // consume 'automate'
18
19    // Expect target identifier
20    let Some(target_token) = parser.peek_clone() else {
21        return crate::core::parser::statement::error_from_token(
22            current_token,
23            "Expected target after 'automate'".to_string(),
24        );
25    };
26
27    if target_token.kind != TokenKind::Identifier && target_token.kind != TokenKind::String {
28        return crate::core::parser::statement::error_from_token(
29            target_token,
30            "Expected valid target after 'automate'".to_string(),
31        );
32    }
33    parser.advance(); // consume target
34
35    // Expect ':'
36    let Some(colon_token) = parser.peek_clone() else {
37        return crate::core::parser::statement::error_from_token(
38            target_token,
39            "Expected ':' after automate target".to_string(),
40        );
41    };
42    if colon_token.kind != TokenKind::Colon {
43        return crate::core::parser::statement::error_from_token(
44            colon_token,
45            "Expected ':' after automate target".to_string(),
46        );
47    }
48    parser.advance(); // consume ':'
49
50    let base_indent = current_token.indent;
51
52    // Collect tokens inside block (indented > base_indent)
53    let mut index = parser.token_index;
54    let mut tokens_inside = Vec::new();
55    while index < parser.tokens.len() {
56        let tok = parser.tokens[index].clone();
57        if tok.indent <= base_indent && tok.kind != TokenKind::Newline {
58            break;
59        }
60        tokens_inside.push(tok);
61        index += 1;
62    }
63    parser.token_index = index;
64
65    // Now parse block manually to capture 'param' entries without reusing general parser kinds
66    let mut local = Parser {
67        resolve_modules: parser.resolve_modules,
68        tokens: tokens_inside,
69        token_index: 0,
70        current_module: parser.current_module.clone(),
71        previous: None,
72    };
73
74    let mut params: HashMap<String, Value> = HashMap::new();
75
76    while let Some(tok) = local.peek_clone() {
77        match tok.kind {
78            TokenKind::Identifier if tok.lexeme == "param" => {
79                local.advance(); // consume 'param'
80                // param name
81                let Some(name_tok) = local.peek_clone() else {
82                    return crate::core::parser::statement::error_from_token(
83                        tok,
84                        "Expected parameter name after 'param'".to_string(),
85                    );
86                };
87                if name_tok.kind != TokenKind::Identifier && name_tok.kind != TokenKind::String {
88                    return crate::core::parser::statement::error_from_token(
89                        name_tok,
90                        "Expected valid parameter name".to_string(),
91                    );
92                }
93                local.advance(); // consume name
94
95                // Expect '{'
96                if !local.match_token(TokenKind::LBrace) {
97                    return crate::core::parser::statement::error_from_token(
98                        name_tok,
99                        "Expected '{' to start parameter block".to_string(),
100                    );
101                }
102
103                // Collect entries like: 0% = 0.0
104                let mut envelope: HashMap<String, Value> = HashMap::new();
105                while let Some(inner) = local.peek_clone() {
106                    if inner.kind == TokenKind::RBrace {
107                        local.advance();
108                        break;
109                    }
110                    // Skip formatting tokens inside the param block
111                    if matches!(
112                        inner.kind,
113                        TokenKind::Newline
114                            | TokenKind::Indent
115                            | TokenKind::Dedent
116                            | TokenKind::Comma
117                    ) {
118                        local.advance();
119                        continue;
120                    }
121
122                    // Read percentage token: could be number followed by '%' as Dot or Identifier? '%' not defined.
123                    // Our lexer has no Percent token, so accept either Number or Identifier containing e.g. '0%'.
124                    let percent_token = inner.clone();
125                    local.advance();
126
127                    let percent_key = percent_token.lexeme.clone();
128
129                    // Expect '='
130                    // Skip any stray formatting between key and '='
131                    while let Some(t) = local.peek_kind() {
132                        if matches!(
133                            t,
134                            TokenKind::Indent | TokenKind::Dedent | TokenKind::Newline
135                        ) {
136                            local.advance();
137                            continue;
138                        }
139                        break;
140                    }
141                    if !local.match_token(TokenKind::Equals) {
142                        return crate::core::parser::statement::error_from_token(
143                            percent_token,
144                            "Expected '=' in param entry".to_string(),
145                        );
146                    }
147
148                    // Read value (number or identifier)
149                    // Skip formatting before value
150                    while let Some(t) = local.peek_kind() {
151                        if matches!(
152                            t,
153                            TokenKind::Indent | TokenKind::Dedent | TokenKind::Newline
154                        ) {
155                            local.advance();
156                            continue;
157                        }
158                        break;
159                    }
160
161                    let value = if let Some(vtok) = local.peek_clone() {
162                        match vtok.kind {
163                            // Handle negative numbers where '-' is lexed as Arrow
164                            TokenKind::Arrow => {
165                                // Check if next token is a number
166                                let mut num_str = String::from("-");
167                                local.advance(); // consume '-'
168                                if let Some(ntok) = local.peek_clone() {
169                                    if ntok.kind == TokenKind::Number {
170                                        num_str.push_str(&ntok.lexeme);
171                                        local.advance(); // consume number
172                                        if let Some(dot) = local.peek_clone() {
173                                            if dot.kind == TokenKind::Dot {
174                                                local.advance();
175                                                if let Some(frac) = local.peek_clone() {
176                                                    if frac.kind == TokenKind::Number {
177                                                        num_str.push('.');
178                                                        num_str.push_str(&frac.lexeme);
179                                                        local.advance();
180                                                    }
181                                                }
182                                            }
183                                        }
184                                        Value::Number(num_str.parse::<f32>().unwrap_or(0.0))
185                                    } else {
186                                        Value::Unknown
187                                    }
188                                } else {
189                                    Value::Unknown
190                                }
191                            }
192                            TokenKind::Number => {
193                                // Possibly a float with dot
194                                let mut number_str = vtok.lexeme.clone();
195                                local.advance();
196                                if let Some(dot) = local.peek_clone() {
197                                    if dot.kind == TokenKind::Dot {
198                                        local.advance();
199                                        if let Some(frac) = local.peek_clone() {
200                                            if frac.kind == TokenKind::Number {
201                                                number_str.push('.');
202                                                number_str.push_str(&frac.lexeme);
203                                                local.advance();
204                                            }
205                                        }
206                                    }
207                                }
208                                Value::Number(number_str.parse::<f32>().unwrap_or(0.0))
209                            }
210                            TokenKind::Identifier => {
211                                local.advance();
212                                Value::Identifier(vtok.lexeme.clone())
213                            }
214                            TokenKind::String => {
215                                local.advance();
216                                Value::String(vtok.lexeme.clone())
217                            }
218                            _ => {
219                                local.advance();
220                                Value::Unknown
221                            }
222                        }
223                    } else {
224                        Value::Null
225                    };
226
227                    envelope.insert(percent_key, value);
228                }
229
230                params.insert(name_tok.lexeme.clone(), Value::Map(envelope));
231            }
232            _ => {
233                local.advance();
234            }
235        }
236    }
237
238    let mut value_map = HashMap::new();
239    value_map.insert(
240        "target".to_string(),
241        Value::String(target_token.lexeme.clone()),
242    );
243    value_map.insert("params".to_string(), Value::Map(params));
244
245    Statement {
246        kind: StatementKind::Automate {
247            target: target_token.lexeme.clone(),
248        },
249        value: Value::Map(value_map),
250        indent: current_token.indent,
251        line: current_token.line,
252        column: current_token.column,
253    }
254}