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}