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        if !parser.match_token(TokenKind::Colon) {
49            logger.log_message(
50                LogLevel::Error,
51                &format!("Expected ':' after map key '{}'", key),
52            );
53            break;
54        }
55
56        // Skip separators and formatting before value
57        while parser.check_token(TokenKind::Newline)
58            || parser.check_token(TokenKind::Whitespace)
59            || parser.check_token(TokenKind::Indent)
60            || parser.check_token(TokenKind::Dedent)
61            || parser.check_token(TokenKind::Comma)
62        {
63            parser.advance();
64        }
65
66        let value = if let Some(token) = parser.peek_clone() {
67            match token.kind {
68                TokenKind::String => {
69                    parser.advance();
70                    Value::String(token.lexeme.clone())
71                }
72                TokenKind::Number => {
73                    // Handle number, decimal number and optional fraction form (e.g., 1/4)
74                    let mut number_str = token.lexeme.clone();
75                    parser.advance(); // consume the first number
76
77                    // decimal support: number '.' number
78                    if let Some(dot_token) = parser.peek_clone() {
79                        if dot_token.kind == TokenKind::Dot {
80                            parser.advance(); // consume the dot
81
82                            if let Some(decimal_token) = parser.peek_clone() {
83                                if decimal_token.kind == TokenKind::Number {
84                                    parser.advance(); // consume the number after the dot
85                                    number_str.push('.');
86                                    number_str.push_str(&decimal_token.lexeme);
87                                } else {
88                                    logger.log_message(
89                                        LogLevel::Error,
90                                        &format!(
91                                            "Expected number after dot, got {:?}",
92                                            decimal_token
93                                        ),
94                                    );
95                                    return Some(Value::Null);
96                                }
97                            } else {
98                                logger.log_message(
99                                    LogLevel::Error,
100                                    "Expected number after dot, but reached EOF",
101                                );
102                                return Some(Value::Null);
103                            }
104                        }
105                    }
106
107                    // Fraction support: number '/' number  -> Duration::Beat("num/den")
108                    if let Some(slash_tok) = parser.peek_clone() {
109                        if slash_tok.kind == TokenKind::Slash {
110                            // consume '/'
111                            parser.advance();
112                            if let Some(den_tok) = parser.peek_clone() {
113                                match den_tok.kind {
114                                    TokenKind::Number | TokenKind::Identifier => {
115                                        let frac = format!("{}/{}", number_str, den_tok.lexeme);
116                                        parser.advance();
117                                        return Some(Value::Duration(
118                                            devalang_types::Duration::Beat(frac),
119                                        ));
120                                    }
121                                    _ => {
122                                        logger.log_message(
123                                            LogLevel::Error,
124                                            &format!(
125                                                "Expected number or identifier after '/', got {:?}",
126                                                den_tok
127                                            ),
128                                        );
129                                        return Some(Value::Null);
130                                    }
131                                }
132                            } else {
133                                logger.log_message(
134                                    LogLevel::Error,
135                                    "Expected denominator after '/', but reached EOF",
136                                );
137                                return Some(Value::Null);
138                            }
139                        }
140                    }
141
142                    Value::Number(number_str.parse::<f32>().unwrap_or(0.0))
143                }
144
145                TokenKind::Identifier => {
146                    // Support dotted identifiers in map values: alias.param or nested
147                    let current_line = token.line;
148                    let mut parts: Vec<String> = vec![token.lexeme.clone()];
149                    parser.advance();
150                    loop {
151                        let Some(next) = parser.peek_clone() else {
152                            break;
153                        };
154                        if next.line != current_line {
155                            break;
156                        }
157                        if next.kind == TokenKind::Dot {
158                            // Consume '.' and the following identifier/number on same line
159                            parser.advance(); // dot
160                            if let Some(id2) = parser.peek_clone() {
161                                if id2.line == current_line
162                                    && (id2.kind == TokenKind::Identifier
163                                        || id2.kind == TokenKind::Number)
164                                {
165                                    parts.push(id2.lexeme.clone());
166                                    parser.advance(); // consume part
167                                    continue;
168                                }
169                            }
170                            break;
171                        } else {
172                            break;
173                        }
174                    }
175                    Value::Identifier(parts.join("."))
176                }
177                TokenKind::LBracket => {
178                    // Allow arrays as map values
179                    if let Some(v) =
180                        crate::core::parser::driver::parse_array::parse_array_value(parser)
181                    {
182                        v
183                    } else {
184                        Value::Null
185                    }
186                }
187                TokenKind::LBrace => {
188                    // Allow inline nested maps as map values
189                    if let Some(v) = parse_map_value(parser) {
190                        v
191                    } else {
192                        Value::Null
193                    }
194                }
195                _ => {
196                    logger.log_message(
197                        LogLevel::Error,
198                        &format!("Unexpected token in map value: {:?}", token),
199                    );
200                    Value::Null
201                }
202            }
203        } else {
204            Value::Null
205        };
206
207        map.insert(key, value);
208
209        // Optionally skip a trailing comma after the value
210        while parser.check_token(TokenKind::Comma)
211            || parser.check_token(TokenKind::Whitespace)
212            || parser.check_token(TokenKind::Newline)
213        {
214            parser.advance();
215        }
216    }
217
218    if !parser.match_token(TokenKind::RBrace) {
219        logger.log_message(LogLevel::Error, "Expected '}' at end of map");
220    }
221
222    Some(Value::Map(map))
223}