1use crate::error::HkError;
2use crate::value::{HkConfig, HkValue};
3use indexmap::IndexMap;
4use std::fs::File;
5use std::io::{BufRead, BufReader};
6use std::path::Path;
7use std::str::FromStr;
8
9pub fn parse_hk(input: &str) -> Result<HkConfig, HkError> {
11 let lines: Vec<&str> = input.lines().collect();
12 let mut config = IndexMap::new();
13 let mut i = 0;
14
15 while i < lines.len() {
16 let line = lines[i].trim_start();
17 if line.is_empty() || line.starts_with('!') {
18 i += 1;
19 continue;
20 }
21
22 if line.starts_with('[') {
23 let close = line.find(']').ok_or_else(|| HkError::Parse {
24 line: (i + 1) as u32,
25 column: line.find('[').unwrap() + 1,
26 message: "Unclosed section header".to_string(),
27 })?;
28 let section_name = line[1..close].trim();
29 if section_name.is_empty() {
30 return Err(HkError::Parse {
31 line: (i + 1) as u32,
32 column: close + 1,
33 message: "Empty section name".to_string(),
34 });
35 }
36
37 let mut end = i + 1;
53 let mut array_depth: i32 = 0;
54 while end < lines.len() {
55 let next_line = lines[end];
56 let next_trimmed = next_line.trim_start();
57 if array_depth == 0 && next_trimmed.starts_with('[') {
58 break;
59 }
60 array_depth += net_bracket_depth(next_line);
61 end += 1;
62 }
63
64 let section_lines = &lines[i + 1..end];
65 let map = parse_map(1, section_lines, i + 2)?;
68 config.insert(section_name.to_string(), HkValue::Map(map));
69 i = end;
70 } else {
71 return Err(HkError::Parse {
72 line: (i + 1) as u32,
73 column: 1,
74 message: "Expected section header".to_string(),
75 });
76 }
77 }
78
79 Ok(config)
80}
81
82fn parse_map(level: usize, lines: &[&str], start_line: usize) -> Result<IndexMap<String, HkValue>, HkError> {
86 let mut map = IndexMap::new();
87 let mut i = 0;
88
89 while i < lines.len() {
90 let line = lines[i];
91 let trimmed = line.trim_start();
92 if trimmed.is_empty() || trimmed.starts_with('!') {
93 i += 1;
94 continue;
95 }
96
97 let dash_count = trimmed.chars().take_while(|c| *c == '-').count();
99 if dash_count == 0 {
100 return Err(HkError::Parse {
101 line: (start_line + i) as u32,
102 column: 1,
103 message: "Expected key or map header".to_string(),
104 });
105 }
106 if dash_count < level {
107 break;
109 }
110 if dash_count > level {
111 return Err(HkError::Parse {
118 line: (start_line + i) as u32,
119 column: 1,
120 message: format!(
121 "Inconsistent nesting level: expected {} dash(es) (\"{}\") at this depth, found {} (\"{}\"). Nesting must increase by exactly one dash per level.",
122 level,
123 "-".repeat(level),
124 dash_count,
125 "-".repeat(dash_count)
126 ),
127 });
128 }
129
130 let after_dashes = &trimmed[dash_count..];
132 let rest = after_dashes.trim_start();
133 if !rest.starts_with('>') {
134 return Err(HkError::Parse {
135 line: (start_line + i) as u32,
136 column: dash_count + 1,
137 message: "Expected '>' after dashes".to_string(),
138 });
139 }
140 let after_gt = &rest[1..].trim_start();
141 if after_gt.is_empty() {
142 return Err(HkError::Parse {
143 line: (start_line + i) as u32,
144 column: dash_count + 1,
145 message: "Missing key after '>'".to_string(),
146 });
147 }
148
149 if let Some(arrow_pos) = after_gt.find("=>") {
151 let key = after_gt[..arrow_pos].trim();
152 let value_part = after_gt[arrow_pos + 2..].trim();
153 let key = unquote_key(key);
154 if key.is_empty() {
155 return Err(HkError::Parse {
156 line: (start_line + i) as u32,
157 column: dash_count + 1,
158 message: "Empty key".to_string(),
159 });
160 }
161 let value_col = arrow_pos + dash_count + 2;
162
163 if value_part.starts_with('[') && net_bracket_depth(value_part) > 0 {
178 let mut buf = value_part.to_string();
179 let mut consumed = 1usize;
180 let mut j = i + 1;
181 while net_bracket_depth(&buf) > 0 {
182 if j >= lines.len() {
183 return Err(HkError::Parse {
184 line: (start_line + i) as u32,
185 column: value_col,
186 message: "Unclosed array: reached end of section before a matching ']'".to_string(),
187 });
188 }
189 buf.push('\n');
190 buf.push_str(lines[j]);
191 consumed += 1;
192 j += 1;
193 }
194 let first = buf.find('[').unwrap();
196 let last = buf.rfind(']').unwrap();
197 let inner = &buf[first + 1..last];
198 let items = parse_array_inner(inner, start_line + i, value_col)?;
199 insert_key(&mut map, &key, HkValue::Array(items))?;
200 i += consumed;
201 } else {
202 let value = parse_value(value_part, start_line + i, value_col)?;
203 insert_key(&mut map, &key, value)?;
204 i += 1;
205 }
206 } else {
207 let key = after_gt.trim();
209 let key = unquote_key(key);
210 if key.is_empty() {
211 return Err(HkError::Parse {
212 line: (start_line + i) as u32,
213 column: dash_count + 1,
214 message: "Empty map key".to_string(),
215 });
216 }
217
218 let next_level = level + 1;
220 let mut j = i + 1;
221 while j < lines.len() {
222 let sub_line = lines[j];
223 let sub_trimmed = sub_line.trim_start();
224 if sub_trimmed.is_empty() || sub_trimmed.starts_with('!') {
225 j += 1;
226 continue;
227 }
228 let sub_dash_count = sub_trimmed.chars().take_while(|c| *c == '-').count();
229 if sub_dash_count < next_level {
230 break;
231 }
232 j += 1;
233 }
234
235 let sub_lines = &lines[i + 1..j];
236 let sub_map = parse_map(next_level, sub_lines, start_line + i + 1)?;
237 insert_key(&mut map, &key, HkValue::Map(sub_map))?;
238 i = j;
239 }
240 }
241
242 Ok(map)
243}
244
245fn insert_key(map: &mut IndexMap<String, HkValue>, key: &str, value: HkValue) -> Result<(), HkError> {
248 if key.contains('.') && !key.starts_with('.') && !key.ends_with('.') {
250 let parts: Vec<&str> = key.split('.').collect();
251 insert_nested(map, parts, value)
252 } else {
253 if map.contains_key(key) {
255 return Err(HkError::KeyConflict(key.to_string()));
256 }
257 map.insert(key.to_string(), value);
258 Ok(())
259 }
260}
261
262fn insert_nested(map: &mut IndexMap<String, HkValue>, keys: Vec<&str>, value: HkValue) -> Result<(), HkError> {
264 let mut current = map;
265 for key in &keys[0..keys.len() - 1] {
266 let entry = current
267 .entry(key.to_string())
268 .or_insert(HkValue::Map(IndexMap::new()));
269 if let HkValue::Map(submap) = entry {
270 current = submap;
271 } else {
272 return Err(HkError::KeyConflict(key.to_string()));
273 }
274 }
275 if let Some(last_key) = keys.last() {
276 current.insert(last_key.to_string(), value);
277 }
278 Ok(())
279}
280
281fn unquote_key(s: &str) -> String {
283 let s = s.trim();
284 if s.starts_with('"') && s.ends_with('"') && s.len() >= 2 {
285 let inner = &s[1..s.len() - 1];
286 inner.replace("\\\"", "\"")
287 } else {
288 s.to_string()
289 }
290}
291
292fn parse_value(s: &str, line: usize, column: usize) -> Result<HkValue, HkError> {
293 let s = s.trim();
294 if s.is_empty() {
295 return Err(HkError::Parse {
296 line: line as u32,
297 column,
298 message: "Empty value".to_string(),
299 });
300 }
301
302 if s.starts_with('[') && s.ends_with(']') && net_bracket_depth(s) == 0 {
307 let inner = &s[1..s.len() - 1];
308 let items = parse_array_inner(inner, line, column)?;
309 Ok(HkValue::Array(items))
310 } else {
311 parse_simple_value(s, line, column)
312 }
313}
314
315fn net_bracket_depth(s: &str) -> i32 {
320 let mut depth = 0i32;
321 let mut in_quotes = false;
322 let mut escape = false;
323 for c in s.chars() {
324 if escape {
325 escape = false;
326 continue;
327 }
328 match c {
329 '\\' if in_quotes => escape = true,
330 '"' => in_quotes = !in_quotes,
331 '[' if !in_quotes => depth += 1,
332 ']' if !in_quotes => depth -= 1,
333 _ => {}
334 }
335 }
336 depth
337}
338
339fn parse_array_inner(inner: &str, line: usize, column: usize) -> Result<Vec<HkValue>, HkError> {
351 let mut items = Vec::new();
352 let mut current = String::new();
353 let mut in_quotes = false;
354 let mut escape = false;
355 let mut depth = 0i32;
356
357 macro_rules! flush_item {
358 () => {{
359 let trimmed = current.trim();
360 let trimmed = trimmed.strip_suffix(',').unwrap_or(trimmed).trim();
361 if !trimmed.is_empty() && !trimmed.starts_with('!') {
362 items.push(parse_value(trimmed, line, column)?);
363 }
364 current.clear();
365 }};
366 }
367
368 for c in inner.chars() {
369 if escape {
370 current.push(c);
371 escape = false;
372 continue;
373 }
374 match c {
375 '\\' if in_quotes => {
376 current.push(c);
377 escape = true;
378 }
379 '"' => {
380 in_quotes = !in_quotes;
381 current.push(c);
382 }
383 '[' if !in_quotes => {
384 depth += 1;
385 current.push(c);
386 }
387 ']' if !in_quotes => {
388 depth -= 1;
389 current.push(c);
390 }
391 ',' if !in_quotes && depth == 0 => flush_item!(),
392 '\n' if !in_quotes && depth == 0 => flush_item!(),
393 _ => current.push(c),
394 }
395 }
396 flush_item!();
397 Ok(items)
398}
399
400fn parse_simple_value(s: &str, line: usize, column: usize) -> Result<HkValue, HkError> {
401 let s = s.trim();
402 if s.is_empty() {
403 return Err(HkError::Parse {
404 line: line as u32,
405 column,
406 message: "Empty value".to_string(),
407 });
408 }
409
410 if s.eq_ignore_ascii_case("true") {
412 return Ok(HkValue::Bool(true));
413 }
414 if s.eq_ignore_ascii_case("false") {
415 return Ok(HkValue::Bool(false));
416 }
417
418 if let Ok(n) = f64::from_str(s) {
420 return Ok(HkValue::Number(n));
421 }
422
423 if s.starts_with('"') && s.ends_with('"') {
425 let inner = &s[1..s.len() - 1];
426 let mut result = String::new();
427 let mut chars = inner.chars();
428 while let Some(c) = chars.next() {
429 if c == '\\' {
430 if let Some(next) = chars.next() {
431 match next {
432 'n' => result.push('\n'),
433 'r' => result.push('\r'),
434 't' => result.push('\t'),
435 '"' => result.push('"'),
436 '\\' => result.push('\\'),
437 _ => result.push(next),
438 }
439 }
440 } else {
441 result.push(c);
442 }
443 }
444 Ok(HkValue::String(result))
445 } else {
446 Ok(HkValue::String(s.to_string()))
448 }
449}
450
451pub fn load_hk_file<P: AsRef<Path>>(path: P) -> Result<HkConfig, HkError> {
453 let file = File::open(path)?;
454 let reader = BufReader::new(file);
455 let mut contents = String::new();
456 for line in reader.lines() {
457 let line = line?;
458 contents.push_str(&line);
459 contents.push('\n');
460 }
461 parse_hk(&contents)
462}