devalang_core/core/parser/driver/
parse_map.rs

1use crate::core::lexer::token::TokenKind;
2use devalang_types::Value;
3
4pub fn parse_map_value(parser: &mut crate::core::parser::driver::parser::Parser) -> Option<Value> {
5    let logger = devalang_utils::logger::Logger::new();
6    use devalang_utils::logger::LogLevel;
7    if !parser.match_token(TokenKind::LBrace) {
8        return None;
9    }
10
11    let mut map = std::collections::HashMap::new();
12
13    while !parser.check_token(TokenKind::RBrace) && !parser.is_eof() {
14        // Skip separators and formatting before the key
15        while parser.check_token(TokenKind::Newline)
16            || parser.check_token(TokenKind::Whitespace)
17            || parser.check_token(TokenKind::Indent)
18            || parser.check_token(TokenKind::Dedent)
19            || parser.check_token(TokenKind::Comma)
20        {
21            parser.advance();
22        }
23
24        // Check if we are at the closing brace of the map
25        if parser.check_token(TokenKind::RBrace) {
26            break;
27        }
28
29        let key = if let Some(token) = parser.advance() {
30            match token.kind {
31                TokenKind::Whitespace
32                | TokenKind::Indent
33                | TokenKind::Dedent
34                | TokenKind::Newline => {
35                    continue;
36                }
37                _ => token.lexeme.clone(),
38            }
39        } else {
40            break;
41        };
42
43        // Skip newlines and whitespace before colon
44        while parser.check_token(TokenKind::Newline) || parser.check_token(TokenKind::Whitespace) {
45            parser.advance();
46        }
47
48        // Accept both 'key: value' and short form 'key value' for convenience.
49        let mut saw_colon = false;
50        if parser.match_token(TokenKind::Colon) {
51            saw_colon = true;
52        } else {
53            // Peek to see if the next token looks like a value; if so, accept short form.
54            if let Some(peek) = parser.peek_clone() {
55                match peek.kind {
56                    TokenKind::String
57                    | TokenKind::Number
58                    | TokenKind::Identifier
59                    | TokenKind::LBracket
60                    | TokenKind::LBrace => {
61                        // short form accepted, do not consume here; value parsing will handle it
62                    }
63                    _ => {
64                        logger.log_message(
65                            LogLevel::Error,
66                            &format!("Expected ':' after map key '{}'", key),
67                        );
68                        break;
69                    }
70                }
71            } else {
72                logger.log_message(
73                    LogLevel::Error,
74                    &format!("Expected ':' after map key '{}'", key),
75                );
76                break;
77            }
78        }
79
80        // Skip separators and formatting before value
81        while parser.check_token(TokenKind::Newline)
82            || parser.check_token(TokenKind::Whitespace)
83            || parser.check_token(TokenKind::Indent)
84            || parser.check_token(TokenKind::Dedent)
85            || parser.check_token(TokenKind::Comma)
86        {
87            parser.advance();
88        }
89
90        let value = if let Some(token) = parser.peek_clone() {
91            match token.kind {
92                TokenKind::String => {
93                    parser.advance();
94                    Value::String(token.lexeme.clone())
95                }
96                TokenKind::Number => {
97                    // Handle number, decimal number and optional fraction form (e.g., 1/4)
98                    let mut number_str = token.lexeme.clone();
99                    parser.advance(); // consume the first number
100
101                    // decimal support: number '.' number
102                    if let Some(dot_token) = parser.peek_clone() {
103                        if dot_token.kind == TokenKind::Dot {
104                            parser.advance(); // consume the dot
105
106                            if let Some(decimal_token) = parser.peek_clone() {
107                                if decimal_token.kind == TokenKind::Number {
108                                    parser.advance(); // consume the number after the dot
109                                    number_str.push('.');
110                                    number_str.push_str(&decimal_token.lexeme);
111                                } else {
112                                    logger.log_message(
113                                        LogLevel::Error,
114                                        &format!(
115                                            "Expected number after dot, got {:?}",
116                                            decimal_token
117                                        ),
118                                    );
119                                    return Some(Value::Null);
120                                }
121                            } else {
122                                logger.log_message(
123                                    LogLevel::Error,
124                                    "Expected number after dot, but reached EOF",
125                                );
126                                return Some(Value::Null);
127                            }
128                        }
129                    }
130
131                    // Fraction support: number '/' number  -> Duration::Beat("num/den")
132                    if let Some(slash_tok) = parser.peek_clone() {
133                        if slash_tok.kind == TokenKind::Slash {
134                            // consume '/'
135                            parser.advance();
136                            if let Some(den_tok) = parser.peek_clone() {
137                                match den_tok.kind {
138                                    TokenKind::Number | TokenKind::Identifier => {
139                                        let frac = format!("{}/{}", number_str, den_tok.lexeme);
140                                        parser.advance();
141                                        return Some(Value::Duration(
142                                            devalang_types::Duration::Beat(frac),
143                                        ));
144                                    }
145                                    _ => {
146                                        logger.log_message(
147                                            LogLevel::Error,
148                                            &format!(
149                                                "Expected number or identifier after '/', got {:?}",
150                                                den_tok
151                                            ),
152                                        );
153                                        return Some(Value::Null);
154                                    }
155                                }
156                            } else {
157                                logger.log_message(
158                                    LogLevel::Error,
159                                    "Expected denominator after '/', but reached EOF",
160                                );
161                                return Some(Value::Null);
162                            }
163                        }
164                    }
165
166                    Value::Number(number_str.parse::<f32>().unwrap_or(0.0))
167                }
168
169                TokenKind::Identifier => {
170                    // Support dotted identifiers in map values: alias.param or nested
171                    let current_line = token.line;
172                    let mut parts: Vec<String> = vec![token.lexeme.clone()];
173                    parser.advance();
174                    loop {
175                        let Some(next) = parser.peek_clone() else {
176                            break;
177                        };
178                        if next.line != current_line {
179                            break;
180                        }
181                        if next.kind == TokenKind::Dot {
182                            // Consume '.' and the following identifier/number on same line
183                            parser.advance(); // dot
184                            if let Some(id2) = parser.peek_clone() {
185                                if id2.line == current_line
186                                    && (id2.kind == TokenKind::Identifier
187                                        || id2.kind == TokenKind::Number)
188                                {
189                                    parts.push(id2.lexeme.clone());
190                                    parser.advance(); // consume part
191                                    continue;
192                                }
193                            }
194                            break;
195                        } else {
196                            break;
197                        }
198                    }
199                    Value::Identifier(parts.join("."))
200                }
201                TokenKind::LBracket => {
202                    // Allow arrays as map values
203                    if let Some(v) =
204                        crate::core::parser::driver::parse_array::parse_array_value(parser)
205                    {
206                        v
207                    } else {
208                        Value::Null
209                    }
210                }
211                TokenKind::LBrace => {
212                    // Allow inline nested maps as map values
213                    if let Some(v) = parse_map_value(parser) {
214                        v
215                    } else {
216                        Value::Null
217                    }
218                }
219                _ => {
220                    logger.log_message(
221                        LogLevel::Error,
222                        &format!("Unexpected token in map value: {:?}", token),
223                    );
224                    Value::Null
225                }
226            }
227        } else {
228            Value::Null
229        };
230
231        map.insert(key, value);
232
233        // Optionally skip a trailing comma after the value
234        while parser.check_token(TokenKind::Comma)
235            || parser.check_token(TokenKind::Whitespace)
236            || parser.check_token(TokenKind::Newline)
237        {
238            parser.advance();
239        }
240    }
241
242    if !parser.match_token(TokenKind::RBrace) {
243        logger.log_message(LogLevel::Error, "Expected '}' at end of map");
244    }
245
246    Some(Value::Map(map))
247}