1use crate::ast::{Block, Document, Value};
6use crate::diagnostic::{DiagnosticCode, DiagnosticReport};
7use crate::lexer::{Lexer, Location, Span, Token, TokenWithSpan};
8use crate::parse_limits::{nesting_limit_error, ParseLimits, DEFAULT_PARSE_LIMITS};
9use std::collections::HashMap;
10
11pub use crate::diagnostic::ParseError;
12
13pub struct Parser<'a> {
15 tokens: Vec<TokenWithSpan>,
16 pos: usize,
17 limits: ParseLimits,
18 initial_error: Option<ParseError>,
19 block_depth: usize,
20 _marker: std::marker::PhantomData<&'a ()>,
21}
22
23impl<'a> Parser<'a> {
24 pub fn new(input: &'a str) -> Self {
25 Self::with_limits(input, DEFAULT_PARSE_LIMITS)
26 }
27
28 pub fn with_limits(input: &'a str, limits: ParseLimits) -> Self {
30 if input.len() > limits.max_document_bytes {
31 return Self {
32 tokens: vec![TokenWithSpan::new(
33 Token::Eof,
34 Default::default(),
35 Default::default(),
36 )],
37 pos: 0,
38 limits,
39 initial_error: Some(ParseError::at(
40 DiagnosticCode::DocumentBytesLimit,
41 format!(
42 "ACL parse limit exceeded: document is larger than {} bytes",
43 limits.max_document_bytes
44 ),
45 Location {
46 line: 1,
47 column: 1,
48 offset: 0,
49 },
50 )),
51 block_depth: 0,
52 _marker: std::marker::PhantomData,
53 };
54 }
55
56 let mut lexer = Lexer::with_max_token_bytes(input, limits.max_token_bytes);
57 let tokens = lexer.tokenize();
58 let initial_error = lexer.token_limit_span().map(|span| {
59 ParseError::new(
60 DiagnosticCode::TokenBytesLimit,
61 format!(
62 "ACL parse limit exceeded: token is longer than {} bytes",
63 limits.max_token_bytes
64 ),
65 span,
66 )
67 });
68 let initial_error =
69 initial_error.or_else(|| nesting_limit_error(&tokens, limits.max_nesting_depth));
70
71 Self {
72 tokens,
73 pos: 0,
74 limits,
75 initial_error,
76 block_depth: 0,
77 _marker: std::marker::PhantomData,
78 }
79 }
80
81 fn current(&self) -> Option<&TokenWithSpan> {
82 self.tokens.get(self.pos)
83 }
84
85 fn peek(&self, offset: usize) -> Option<&TokenWithSpan> {
86 self.tokens.get(self.pos + offset)
87 }
88
89 fn eof_span(&self) -> Span {
90 self.tokens
91 .last()
92 .map(|token| token.span)
93 .unwrap_or_else(|| {
94 Span::point(Location {
95 line: 1,
96 column: 1,
97 offset: 0,
98 })
99 })
100 }
101
102 fn advance(&mut self) {
103 if self.pos < self.tokens.len() - 1 {
104 self.pos += 1;
105 }
106 }
107
108 fn skip_newlines(&mut self) {
109 while let Some(t) = self.current() {
110 if matches!(t.token, Token::Newline | Token::Comment) {
111 self.advance();
112 } else {
113 break;
114 }
115 }
116 }
117
118 pub fn parse(&mut self) -> Result<Document, ParseError> {
120 if let Some(error) = &self.initial_error {
121 return Err(error.clone());
122 }
123
124 let mut doc = Document::default();
125 self.skip_newlines();
126
127 while !self.is_eof() {
128 self.parse_document_item(&mut doc)?;
129 self.skip_newlines();
130 }
131
132 Ok(doc)
133 }
134
135 pub fn collect_diagnostics(&mut self) -> DiagnosticReport {
137 let mut report = DiagnosticReport::default();
138 if let Some(error) = &self.initial_error {
139 Self::record_diagnostic(&mut report, error.clone(), self.limits.max_diagnostics);
140 return report;
141 }
142
143 let mut doc = Document::default();
144 self.skip_newlines();
145
146 while !self.is_eof() {
147 let item_start = self.pos;
148 match self.parse_document_item(&mut doc) {
149 Ok(()) => self.skip_newlines(),
150 Err(error) => {
151 let error_line = error.span.start.line;
152 let is_limit = error.code.is_limit();
153 if !Self::record_diagnostic(&mut report, error, self.limits.max_diagnostics) {
154 break;
155 }
156 if is_limit {
157 break;
158 }
159 self.recover_after_error(item_start, error_line);
160 }
161 }
162 }
163
164 report
165 }
166
167 fn is_eof(&self) -> bool {
168 self.current()
169 .map(|token| matches!(token.token, Token::Eof))
170 .unwrap_or(true)
171 }
172
173 fn parse_document_item(&mut self, doc: &mut Document) -> Result<(), ParseError> {
174 let token = self.current().cloned().unwrap_or_else(|| {
175 TokenWithSpan::new(Token::Eof, self.eof_span().start, self.eof_span().end)
176 });
177 let span = token.span;
178 match token.token {
179 Token::Ident(name) => {
180 self.ensure_collection_capacity(doc.blocks.len(), span)?;
181 let is_bare_attr = self
182 .peek(1)
183 .map(|next| matches!(next.token, Token::Equal | Token::Colon))
184 .unwrap_or(false);
185
186 if is_bare_attr {
187 self.advance();
188 let attr = self.parse_attribute(name.clone())?;
189 doc.blocks.push(Block {
190 name,
191 labels: vec![],
192 blocks: vec![],
193 attributes: vec![attr].into_iter().collect(),
194 });
195 } else {
196 doc.blocks.push(self.parse_block()?);
197 }
198 Ok(())
199 }
200 _ => Err(ParseError::new(
201 DiagnosticCode::UnexpectedToken,
202 format!("Unexpected token: {}", token.token.kind_name()),
203 span,
204 )),
205 }
206 }
207
208 fn record_diagnostic(
209 report: &mut DiagnosticReport,
210 error: ParseError,
211 max_diagnostics: usize,
212 ) -> bool {
213 if report.diagnostics.len() >= max_diagnostics {
214 report.truncated = true;
215 return false;
216 }
217 report.diagnostics.push(error);
218 true
219 }
220
221 fn recover_after_error(&mut self, item_start: usize, error_line: usize) {
222 if self.pos == item_start && !self.is_eof() {
223 self.advance();
224 }
225 while let Some(token) = self.current() {
226 if matches!(token.token, Token::Eof) || token.span.start.line > error_line {
227 break;
228 }
229 self.advance();
230 }
231 self.block_depth = 0;
232 self.skip_newlines();
233 }
234
235 fn ensure_collection_capacity(&self, current_len: usize, span: Span) -> Result<(), ParseError> {
236 if current_len >= self.limits.max_collection_items {
237 return Err(ParseError::new(
238 DiagnosticCode::CollectionItemsLimit,
239 format!(
240 "ACL parse limit exceeded: collection has more than {} items",
241 self.limits.max_collection_items
242 ),
243 span,
244 ));
245 }
246 Ok(())
247 }
248
249 fn parse_block(&mut self) -> Result<Block, ParseError> {
251 let span = self.current().map(|token| token.span).unwrap_or_default();
252 if self.block_depth >= self.limits.max_nesting_depth {
253 return Err(ParseError::new(
254 DiagnosticCode::NestingDepthLimit,
255 format!(
256 "ACL parse limit exceeded: nesting depth is greater than {}",
257 self.limits.max_nesting_depth
258 ),
259 span,
260 ));
261 }
262
263 self.block_depth += 1;
264 let result = self.parse_block_inner();
265 self.block_depth -= 1;
266 result
267 }
268
269 fn parse_block_inner(&mut self) -> Result<Block, ParseError> {
270 let ident = match self.current() {
271 Some(t) if matches!(t.token, Token::Ident(_)) => {
272 let name = match &t.token {
273 Token::Ident(s) => s.clone(),
274 _ => unreachable!(),
275 };
276 self.advance();
277 name
278 }
279 Some(t) => {
280 return Err(ParseError::new(
281 DiagnosticCode::ExpectedToken,
282 format!("Expected block name, found {}", t.token.kind_name()),
283 t.span,
284 ));
285 }
286 None => {
287 return Err(ParseError::new(
288 DiagnosticCode::UnexpectedEof,
289 "Unexpected end of input",
290 self.eof_span(),
291 ));
292 }
293 };
294
295 let mut labels = Vec::new();
297 while let Some(t) = self.current() {
298 match &t.token {
299 Token::String(s) => {
300 let span = t.span;
301 let label = s.clone();
302 self.ensure_collection_capacity(labels.len(), span)?;
303 labels.push(label);
304 self.advance();
305 }
306 Token::Ident(s)
307 if self
308 .peek(1)
309 .map(|p| matches!(p.token, Token::LeftBrace))
310 .unwrap_or(false) =>
311 {
312 break;
314 }
315 _ => break,
316 }
317 }
318
319 self.skip_newlines();
320
321 let (blocks, attributes) = self.parse_block_body()?;
323
324 Ok(Block {
325 name: ident,
326 labels,
327 blocks,
328 attributes,
329 })
330 }
331
332 fn parse_block_body(&mut self) -> Result<(Vec<Block>, HashMap<String, Value>), ParseError> {
334 let mut blocks = Vec::new();
335 let mut attributes = HashMap::new();
336 let mut item_count = 0;
337
338 self.skip_newlines();
339
340 while let Some(t) = self.current() {
342 match &t.token {
343 Token::RightBrace => {
344 self.advance();
345 break;
346 }
347 Token::LeftBrace => {
348 self.advance();
350 }
351 Token::Ident(name) => {
352 let span = t.span;
354 let name = name.clone();
355 let after_ident = self.peek(1);
356 let after_after = self.peek(2);
357
358 if let Some(next) = after_ident {
359 match &next.token {
360 Token::Equal | Token::Colon => {
361 self.ensure_collection_capacity(item_count, span)?;
363 self.advance(); let attr = self.parse_attribute(name)?;
365 attributes.insert(attr.0, attr.1);
366 item_count += 1;
367 }
368 Token::String(_) => {
369 self.ensure_collection_capacity(item_count, span)?;
371 let block = self.parse_block()?;
372 blocks.push(block);
373 item_count += 1;
374 }
375 Token::LeftBrace => {
376 self.ensure_collection_capacity(item_count, span)?;
378 self.advance(); let block = self.parse_nested_block(name.clone())?;
380 blocks.push(block);
381 item_count += 1;
382 }
383 Token::Ident(_) => {
384 if let Some(after) = after_after {
387 match &after.token {
388 Token::LeftBrace | Token::String(_) => {
389 self.ensure_collection_capacity(item_count, span)?;
391 let block = self.parse_block()?;
392 blocks.push(block);
393 item_count += 1;
394 }
395 Token::Equal | Token::Colon => {
396 self.ensure_collection_capacity(item_count, span)?;
398 self.advance(); let attr = self.parse_attribute(name)?;
400 attributes.insert(attr.0, attr.1);
401 item_count += 1;
402 }
403 _ => {
404 self.ensure_collection_capacity(item_count, span)?;
406 let block = self.parse_block()?;
407 blocks.push(block);
408 item_count += 1;
409 }
410 }
411 } else {
412 self.advance();
414 }
415 }
416 _ => {
417 self.advance();
418 }
419 }
420 } else {
421 self.advance();
422 }
423 }
424 Token::Newline | Token::Comment => {
425 self.advance();
426 }
427 Token::Eof => break,
428 _ => {
429 self.advance();
431 }
432 }
433 self.skip_newlines();
434 }
435
436 Ok((blocks, attributes))
437 }
438
439 fn parse_nested_block(&mut self, name: String) -> Result<Block, ParseError> {
441 let mut labels = Vec::new();
443 while let Some(t) = self.current() {
444 match &t.token {
445 Token::String(s) => {
446 let span = t.span;
447 let label = s.clone();
448 self.ensure_collection_capacity(labels.len(), span)?;
449 labels.push(label);
450 self.advance();
451 }
452 _ => break,
453 }
454 }
455
456 self.skip_newlines();
457
458 let (blocks, attributes) = self.parse_block_body()?;
459
460 Ok(Block {
461 name,
462 labels,
463 blocks,
464 attributes,
465 })
466 }
467
468 fn parse_attribute(&mut self, name: String) -> Result<(String, Value), ParseError> {
470 let token = self.current().cloned();
472 if let Some(t) = token {
473 if !matches!(t.token, Token::Equal | Token::Colon) {
474 return Err(ParseError::new(
475 DiagnosticCode::ExpectedToken,
476 format!("Expected '=' or ':', found {}", t.token.kind_name()),
477 t.span,
478 ));
479 }
480 self.advance();
481 }
482
483 self.skip_newlines();
484 let value = self.parse_value()?;
485
486 Ok((name, value))
487 }
488
489 fn parse_value(&mut self) -> Result<Value, ParseError> {
491 let eof_span = self.eof_span();
492 let token = self.current().ok_or_else(|| {
493 ParseError::new(
494 DiagnosticCode::UnexpectedEof,
495 "Unexpected end of input",
496 eof_span,
497 )
498 })?;
499
500 match token.token.clone() {
501 Token::String(s) => {
502 self.advance();
503 Ok(Value::String(s))
504 }
505 Token::Number(n) => {
506 self.advance();
507 Ok(Value::Number(n))
508 }
509 Token::True => {
510 self.advance();
511 Ok(Value::Bool(true))
512 }
513 Token::False => {
514 self.advance();
515 Ok(Value::Bool(false))
516 }
517 Token::Null => {
518 self.advance();
519 Ok(Value::Null)
520 }
521 Token::LeftBracket => {
522 self.advance();
523 self.parse_list()
524 }
525 Token::LeftBrace => {
526 self.advance();
527 let (_blocks, attrs) = self.parse_block_body()?;
528 Ok(Value::Object(attrs.into_iter().collect()))
529 }
530 Token::Ident(name) => {
531 let name = name.clone();
532 self.advance();
534 self.skip_newlines();
535
536 if let Some(next) = self.current() {
538 match &next.token {
539 Token::LeftParen => {
540 self.advance(); let args = self.parse_call_args()?;
543 return Ok(Value::Call(name, args));
544 }
545 Token::LeftBrace => {
546 let block = self.parse_nested_block(name)?;
548 return Ok(Value::Object(vec![(
549 "_block".to_string(),
550 Value::Object(vec![
551 ("name".to_string(), Value::String(block.name)),
552 (
553 "labels".to_string(),
554 Value::List(
555 block
556 .labels
557 .iter()
558 .map(|s| Value::String(s.clone()))
559 .collect(),
560 ),
561 ),
562 (
563 "attributes".to_string(),
564 Value::Object(block.attributes.into_iter().collect()),
565 ),
566 ]),
567 )]));
568 }
569 _ => {}
570 }
571 }
572
573 Ok(Value::String(name))
574 }
575 Token::Eof => Err(ParseError::new(
576 DiagnosticCode::UnexpectedEof,
577 "Unexpected end of input",
578 token.span,
579 )),
580 _ => Err(ParseError::new(
581 DiagnosticCode::UnexpectedToken,
582 format!(
583 "Unexpected token in value position: {}",
584 token.token.kind_name()
585 ),
586 token.span,
587 )),
588 }
589 }
590
591 fn parse_list(&mut self) -> Result<Value, ParseError> {
593 let mut items = Vec::new();
594 self.skip_newlines();
595
596 while let Some(t) = self.current() {
597 match &t.token {
598 Token::RightBracket => {
599 self.advance();
600 break;
601 }
602 Token::Comma => {
603 self.advance();
604 self.skip_newlines();
605 }
606 Token::Newline | Token::Comment => {
607 self.advance();
608 }
609 _ => {
610 let span = t.span;
611 self.ensure_collection_capacity(items.len(), span)?;
612 let value = self.parse_value()?;
613 items.push(value);
614 self.skip_newlines();
615
616 if let Some(next) = self.current() {
618 match &next.token {
619 Token::Comma => {
620 self.advance(); self.skip_newlines();
622 }
623 Token::RightBracket => {
624 }
626 _ => {}
627 }
628 }
629 }
630 }
631 }
632
633 Ok(Value::List(items))
634 }
635
636 fn parse_call_args(&mut self) -> Result<Vec<Value>, ParseError> {
638 let mut args = Vec::new();
639 self.skip_newlines();
640
641 if let Some(t) = self.current() {
643 if matches!(t.token, Token::RightParen) {
644 self.advance();
645 return Ok(args);
646 }
647 }
648
649 loop {
650 let span = self.current().map(|token| token.span).unwrap_or_default();
651 self.ensure_collection_capacity(args.len(), span)?;
652 let value = self.parse_value()?;
653 args.push(value);
654 self.skip_newlines();
655
656 match self.current() {
657 Some(t) if matches!(t.token, Token::Comma) => {
658 self.advance(); self.skip_newlines();
660 }
661 Some(t) if matches!(t.token, Token::RightParen) => {
662 self.advance(); break;
664 }
665 Some(t) => {
666 return Err(ParseError::new(
667 DiagnosticCode::ExpectedToken,
668 format!("Expected ',' or ')', found {}", t.token.kind_name()),
669 t.span,
670 ));
671 }
672 None => {
673 return Err(ParseError::new(
674 DiagnosticCode::UnexpectedEof,
675 "Unexpected end of input in function call",
676 self.eof_span(),
677 ));
678 }
679 }
680 }
681
682 Ok(args)
683 }
684}
685
686pub fn parse(input: &str) -> Result<Document, ParseError> {
688 parse_with_limits(input, DEFAULT_PARSE_LIMITS)
689}
690
691pub fn parse_with_limits(input: &str, limits: ParseLimits) -> Result<Document, ParseError> {
693 Parser::with_limits(input, limits).parse()
694}
695
696pub fn collect_diagnostics(input: &str) -> DiagnosticReport {
698 collect_diagnostics_with_limits(input, DEFAULT_PARSE_LIMITS)
699}
700
701pub fn collect_diagnostics_with_limits(input: &str, limits: ParseLimits) -> DiagnosticReport {
703 Parser::with_limits(input, limits).collect_diagnostics()
704}
705
706#[cfg(test)]
707mod tests {
708 use super::*;
709
710 #[test]
711 fn test_parse_simple() {
712 let input = r#"
713 name = "test"
714 count = 42
715 "#;
716 let doc = parse(input).unwrap();
717 assert_eq!(doc.blocks.len(), 2);
718 assert_eq!(doc.blocks[0].name, "name");
719 assert_eq!(doc.blocks[1].name, "count");
720 }
721
722 #[test]
723 fn test_parse_block() {
724 let input = r#"
725 providers "openai" {
726 name = "openai"
727 api_key = "sk-test"
728 }
729 "#;
730 let doc = parse(input).unwrap();
731 assert_eq!(doc.blocks.len(), 1);
732 let block = &doc.blocks[0];
733 assert_eq!(block.name, "providers");
734 assert_eq!(block.labels, vec!["openai"]);
735 assert_eq!(
736 block.attributes.get("name").map(|v| v.to_string()).unwrap(),
737 "openai"
738 );
739 }
740
741 #[test]
742 fn test_parse_nested_block() {
743 let input = r#"
744 providers {
745 openai "gpt-4" {
746 model = "gpt-4"
747 }
748 }
749 "#;
750 let doc = parse(input).unwrap();
751 assert_eq!(doc.blocks.len(), 1);
752 let block = &doc.blocks[0];
753 assert_eq!(block.name, "providers");
754 assert!(block.blocks.len() >= 1);
755 }
756
757 #[test]
758 fn test_parse_list() {
759 let input = r#"
760 numbers = [1, 2, 3]
761 "#;
762 let doc = parse(input).unwrap();
763 assert_eq!(doc.blocks.len(), 1);
764 let attr = doc.blocks[0].attributes.get("numbers").unwrap();
765 match attr {
766 Value::List(items) => assert_eq!(items.len(), 3),
767 _ => panic!("Expected list"),
768 }
769 }
770
771 #[test]
772 fn test_parse_string_list() {
773 let input = r#"
775 names = ["alice"]
776 "#;
777 let doc = parse(input).unwrap();
778 let attr = doc.blocks[0].attributes.get("names").unwrap();
779 match attr {
780 Value::List(items) => assert_eq!(items.len(), 1),
781 _ => panic!("Expected list"),
782 }
783 }
784
785 #[test]
786 fn test_parse_boolean_true() {
787 let input = r#"
788 enabled = true
789 "#;
790 let doc = parse(input).unwrap();
791 let attr = doc.blocks[0].attributes.get("enabled").unwrap();
792 assert_eq!(attr, &Value::Bool(true));
793 }
794
795 #[test]
796 fn test_parse_boolean_false() {
797 let input = r#"
798 enabled = false
799 "#;
800 let doc = parse(input).unwrap();
801 let attr = doc.blocks[0].attributes.get("enabled").unwrap();
802 assert_eq!(attr, &Value::Bool(false));
803 }
804
805 #[test]
806 fn test_parse_null() {
807 let input = r#"
808 value = null
809 "#;
810 let doc = parse(input).unwrap();
811 let attr = doc.blocks[0].attributes.get("value").unwrap();
812 assert_eq!(attr, &Value::Null);
813 }
814
815 #[test]
816 fn test_parse_number_integer() {
817 let input = r#"
818 count = 42
819 "#;
820 let doc = parse(input).unwrap();
821 let attr = doc.blocks[0].attributes.get("count").unwrap();
822 match attr {
823 Value::Number(n) => assert_eq!(*n, 42.0),
824 _ => panic!("Expected number"),
825 }
826 }
827
828 #[test]
829 fn test_parse_number_float() {
830 let input = r#"
831 pi = 3.14
832 "#;
833 let doc = parse(input).unwrap();
834 let attr = doc.blocks[0].attributes.get("pi").unwrap();
835 match attr {
836 Value::Number(n) => assert!((*n - 3.14).abs() < 0.001),
837 _ => panic!("Expected number"),
838 }
839 }
840
841 #[test]
842 fn test_parse_negative_number() {
843 let input = r#"
844 temp = -10
845 "#;
846 let doc = parse(input).unwrap();
847 let attr = doc.blocks[0].attributes.get("temp").unwrap();
848 match attr {
849 Value::Number(n) => assert_eq!(*n, -10.0),
850 _ => panic!("Expected number"),
851 }
852 }
853
854 #[test]
855 fn test_parse_string_value() {
856 let input = r#"
857 name = "hello world"
858 "#;
859 let doc = parse(input).unwrap();
860 let attr = doc.blocks[0].attributes.get("name").unwrap();
861 match attr {
862 Value::String(s) => assert_eq!(s, "hello world"),
863 _ => panic!("Expected string"),
864 }
865 }
866
867 #[test]
868 fn test_parse_colon_separator() {
869 let input = r#"
870 config {
871 key: "value"
872 }
873 "#;
874 let doc = parse(input).unwrap();
875 assert_eq!(doc.blocks.len(), 1);
876 assert_eq!(doc.blocks[0].name, "config");
877 }
878
879 #[test]
880 fn test_parse_error_display() {
881 let err = ParseError::new(
882 DiagnosticCode::UnexpectedToken,
883 "test error",
884 Span::point(Location {
885 line: 10,
886 column: 5,
887 offset: 42,
888 }),
889 );
890 let display = format!("{}", err);
891 assert!(display.contains("line 10"));
892 assert!(display.contains("column 5"));
893 assert!(display.contains("test error"));
894 }
895
896 #[test]
897 fn test_parse_error_trait() {
898 let err = ParseError::new(
899 DiagnosticCode::UnexpectedToken,
900 "test",
901 Span::point(Location {
902 line: 1,
903 column: 1,
904 offset: 0,
905 }),
906 );
907 let _ = err.clone();
908 }
909
910 #[test]
911 fn test_parse_empty_document() {
912 let input = "";
913 let doc = parse(input).unwrap();
914 assert!(doc.blocks.is_empty());
915 }
916
917 #[test]
918 fn test_parse_only_newlines() {
919 let input = "\n\n\n";
920 let doc = parse(input).unwrap();
921 assert!(doc.blocks.is_empty());
922 }
923
924 #[test]
925 fn test_parse_comments() {
926 let input = r#"
927 # this is a comment
928 name = "test"
929 // another comment
930 count = 42
931 "#;
932 let doc = parse(input).unwrap();
933 assert_eq!(doc.blocks.len(), 2);
934 }
935
936 #[test]
937 fn test_parse_block_no_braces() {
938 let input = r#"
939 config
940 name = "test"
941 "#;
942 let doc = parse(input).unwrap();
943 assert_eq!(doc.blocks.len(), 1);
944 assert_eq!(doc.blocks[0].name, "config");
945 }
946
947 #[test]
948 fn test_parse_multiple_blocks() {
949 let input = r#"
950 block1 "label1" {}
951 block2 "label2" {}
952 "#;
953 let doc = parse(input).unwrap();
954 assert_eq!(doc.blocks.len(), 2);
955 assert_eq!(doc.blocks[0].name, "block1");
956 assert_eq!(doc.blocks[1].name, "block2");
957 }
958
959 #[test]
960 fn test_parse_identifier_value() {
961 let input = r#"
962 ref = other_name
963 "#;
964 let doc = parse(input).unwrap();
965 let attr = doc.blocks[0].attributes.get("ref").unwrap();
966 match attr {
967 Value::String(s) => assert_eq!(s, "other_name"),
968 _ => panic!("Expected string"),
969 }
970 }
971
972 #[test]
973 fn test_parse_block_with_block_nested_inside() {
974 let input = r#"
975 outer {
976 inner "label" {
977 key = "value"
978 }
979 }
980 "#;
981 let doc = parse(input).unwrap();
982 assert_eq!(doc.blocks.len(), 1);
983 assert_eq!(doc.blocks[0].name, "outer");
984 }
985
986 #[test]
987 fn test_parse_implicit_block_with_labels() {
988 let input = r#"
989 item "first" "second" {
990 name = "test"
991 }
992 "#;
993 let doc = parse(input).unwrap();
994 assert_eq!(doc.blocks[0].labels.len(), 2);
995 }
996
997 #[test]
998 fn test_parse_error_unexpected_token() {
999 let input = r#"
1000 [invalid]
1001 "#;
1002 let result = parse(input);
1003 assert!(result.is_err());
1004 }
1005
1006 #[test]
1007 fn test_parse_error_eof_in_block() {
1008 let input = r#"block key = "#;
1010 let result = parse(input);
1011 assert!(result.is_err());
1012 }
1013
1014 #[test]
1015 fn test_parse_block_no_labels() {
1016 let input = r#"
1017 block {
1018 key = "value"
1019 }
1020 "#;
1021 let doc = parse(input).unwrap();
1022 assert_eq!(doc.blocks.len(), 1);
1023 assert_eq!(doc.blocks[0].name, "block");
1024 assert!(doc.blocks[0].labels.is_empty());
1025 }
1026
1027 #[test]
1028 fn test_parse_multiple_attributes() {
1029 let input = r#"
1030 config {
1031 a = 1
1032 b = 2
1033 c = 3
1034 }
1035 "#;
1036 let doc = parse(input).unwrap();
1037 assert_eq!(doc.blocks[0].attributes.len(), 3);
1038 }
1039
1040 #[test]
1041 fn test_parse_list_of_numbers() {
1042 let input = r#"
1043 nums = [1, 2, 3]
1044 "#;
1045 let doc = parse(input).unwrap();
1046 let attr = doc.blocks[0].attributes.get("nums").unwrap();
1047 match attr {
1048 Value::List(items) => {
1049 assert_eq!(items.len(), 3);
1050 assert_eq!(items[0], Value::Number(1.0));
1051 assert_eq!(items[1], Value::Number(2.0));
1052 assert_eq!(items[2], Value::Number(3.0));
1053 }
1054 _ => panic!("Expected list"),
1055 }
1056 }
1057
1058 #[test]
1059 fn test_parse_list_of_mixed() {
1060 let input = r#"
1061 mixed = ["string", 42, true]
1062 "#;
1063 let doc = parse(input).unwrap();
1064 let attr = doc.blocks[0].attributes.get("mixed").unwrap();
1065 match attr {
1066 Value::List(items) => assert_eq!(items.len(), 3),
1067 _ => panic!("Expected list"),
1068 }
1069 }
1070
1071 #[test]
1072 fn test_parse_empty_list() {
1073 let input = r#"items = []"#;
1074 let doc = parse(input).unwrap();
1075 let attr = doc.blocks[0].attributes.get("items").unwrap();
1076 match attr {
1077 Value::List(items) => assert!(items.is_empty()),
1078 _ => panic!("Expected list"),
1079 }
1080 }
1081
1082 #[test]
1083 fn test_parse_nested_block_with_labels() {
1084 let input = r#"
1085 parent {
1086 child "label" {
1087 key = "value"
1088 }
1089 }
1090 "#;
1091 let doc = parse(input).unwrap();
1092 assert!(!doc.blocks[0].blocks.is_empty());
1093 }
1094
1095 #[test]
1096 fn test_parse_left_brace_after_ident() {
1097 let input = r#"
1099 myblock {
1100 key = "value"
1101 }
1102 "#;
1103 let doc = parse(input).unwrap();
1104 assert_eq!(doc.blocks.len(), 1);
1105 assert_eq!(doc.blocks[0].name, "myblock");
1106 }
1107
1108 #[test]
1109 fn test_parse_left_brace_after_string() {
1110 let input = r#"
1112 myblock "label" {
1113 key = "value"
1114 }
1115 "#;
1116 let doc = parse(input).unwrap();
1117 assert_eq!(doc.blocks[0].labels, vec!["label"]);
1118 }
1119
1120 #[test]
1121 fn test_parse_ident_after_ident_with_left_brace() {
1122 let input = r#"
1124 type "name" {
1125 key = "value"
1126 }
1127 "#;
1128 let doc = parse(input).unwrap();
1129 assert_eq!(doc.blocks[0].name, "type");
1130 assert_eq!(doc.blocks[0].labels, vec!["name"]);
1131 }
1132
1133 #[test]
1134 fn test_parse_block_sparse_labels() {
1135 let input = r#"
1136 block "a" "b" "c" {}
1137 "#;
1138 let doc = parse(input).unwrap();
1139 assert_eq!(doc.blocks[0].labels, vec!["a", "b", "c"]);
1140 }
1141
1142 #[test]
1143 fn test_parse_value_number_negative() {
1144 let input = r#"val = -123"#;
1145 let doc = parse(input).unwrap();
1146 let attr = doc.blocks[0].attributes.get("val").unwrap();
1147 assert_eq!(attr, &Value::Number(-123.0));
1148 }
1149
1150 #[test]
1151 fn test_parse_value_number_float() {
1152 let input = r#"val = 0.5"#;
1153 let doc = parse(input).unwrap();
1154 let attr = doc.blocks[0].attributes.get("val").unwrap();
1155 match attr {
1156 Value::Number(n) => assert!((*n - 0.5).abs() < 0.001),
1157 _ => panic!("Expected number"),
1158 }
1159 }
1160
1161 #[test]
1162 fn test_parse_whitespace_only() {
1163 let input = " \t\t \n\n\n ";
1164 let doc = parse(input).unwrap();
1165 assert!(doc.blocks.is_empty());
1166 }
1167
1168 #[test]
1169 fn test_parse_only_comments() {
1170 let input = "# comment\n// another\n# third";
1171 let doc = parse(input).unwrap();
1172 assert!(doc.blocks.is_empty());
1173 }
1174
1175 #[test]
1176 fn test_parse_comment_between_statements() {
1177 let input = r#"
1178 a = 1
1179 # comment
1180 b = 2
1181 "#;
1182 let doc = parse(input).unwrap();
1183 assert_eq!(doc.blocks.len(), 2);
1184 }
1185
1186 #[test]
1187 fn test_parse_skip_unexpected_token() {
1188 let input = r#"
1190 block {
1191 key = "value"
1192 unexpected_token
1193 other = "data"
1194 }
1195 "#;
1196 let doc = parse(input).unwrap();
1197 assert_eq!(doc.blocks.len(), 1);
1199 }
1200
1201 #[test]
1202 fn test_parse_only_newline_in_list() {
1203 let input = r#"
1205 nums = [
1206 1,
1207 2,
1208 3
1209 ]
1210 "#;
1211 let doc = parse(input).unwrap();
1212 let attr = doc.blocks[0].attributes.get("nums").unwrap();
1213 match attr {
1214 Value::List(items) => assert_eq!(items.len(), 3),
1215 _ => panic!("Expected list"),
1216 }
1217 }
1218
1219 #[test]
1220 fn test_parse_list_trailing_comma() {
1221 let input = r#"nums = [1, 2, 3,]"#;
1222 let result = parse(input);
1223 assert!(result.is_ok() || result.is_err());
1226 }
1227
1228 #[test]
1229 fn test_parse_block_with_comment_before_attr() {
1230 let input = r#"
1231 block {
1232 # this is a comment
1233 key = "value"
1234 }
1235 "#;
1236 let doc = parse(input).unwrap();
1237 assert_eq!(
1238 doc.blocks[0]
1239 .attributes
1240 .get("key")
1241 .map(|v| v.to_string())
1242 .unwrap(),
1243 "value"
1244 );
1245 }
1246
1247 #[test]
1248 fn test_parse_float_scientific() {
1249 let input = r#"val = 1e-5"#;
1250 let doc = parse(input).unwrap();
1251 let attr = doc.blocks[0].attributes.get("val").unwrap();
1252 match attr {
1253 Value::Number(n) => assert!((*n - 0.00001).abs() < 0.000001),
1254 _ => panic!("Expected number"),
1255 }
1256 }
1257
1258 #[test]
1259 fn test_parse_block_without_body() {
1260 let input = r#"empty_block"#;
1262 let doc = parse(input).unwrap();
1263 assert_eq!(doc.blocks.len(), 1);
1264 assert_eq!(doc.blocks[0].name, "empty_block");
1265 }
1266
1267 #[test]
1268 fn test_parse_nested_block_type_ident() {
1269 let input = r#"
1271 outer {
1272 inner "label" {}
1273 }
1274 "#;
1275 let doc = parse(input).unwrap();
1276 assert!(!doc.blocks[0].blocks.is_empty());
1277 }
1278
1279 #[test]
1280 fn test_parse_block_with_plus_equal() {
1281 let input = r#"key += value"#;
1283 let result = parse(input);
1284 assert!(result.is_ok() || result.is_err());
1286 }
1287
1288 #[test]
1289 fn test_parse_attr_with_colon() {
1290 let input = r#"key: "value""#;
1292 let doc = parse(input).unwrap();
1293 assert_eq!(
1294 doc.blocks[0]
1295 .attributes
1296 .get("key")
1297 .map(|v| v.to_string())
1298 .unwrap(),
1299 "value"
1300 );
1301 }
1302
1303 #[test]
1304 fn test_parse_block_empty_labels() {
1305 let input = r#"block "" {}"#;
1306 let doc = parse(input).unwrap();
1307 assert_eq!(doc.blocks[0].labels, vec![""]);
1308 }
1309
1310 #[test]
1311 fn test_parse_eof_after_block() {
1312 let input = r#"block {}"#;
1313 let doc = parse(input).unwrap();
1314 assert_eq!(doc.blocks.len(), 1);
1315 }
1316
1317 #[test]
1318 fn test_parse_block_single_quoted_label() {
1319 let input = r#"block 'label' {}"#;
1320 let doc = parse(input).unwrap();
1321 assert_eq!(doc.blocks[0].labels, vec!["label"]);
1322 }
1323
1324 #[test]
1325 fn test_parse_string_with_equals() {
1326 let input = r#"key = "a=b""#;
1327 let doc = parse(input).unwrap();
1328 assert_eq!(
1329 doc.blocks[0]
1330 .attributes
1331 .get("key")
1332 .map(|v| v.to_string())
1333 .unwrap(),
1334 "a=b"
1335 );
1336 }
1337
1338 #[test]
1339 fn test_parse_string_with_braces() {
1340 let input = r#"key = "a{b}c""#;
1341 let doc = parse(input).unwrap();
1342 assert_eq!(
1343 doc.blocks[0]
1344 .attributes
1345 .get("key")
1346 .map(|v| v.to_string())
1347 .unwrap(),
1348 "a{b}c"
1349 );
1350 }
1351
1352 #[test]
1353 fn test_parse_string_with_brackets() {
1354 let input = r#"key = "a[b]c""#;
1355 let doc = parse(input).unwrap();
1356 assert_eq!(
1357 doc.blocks[0]
1358 .attributes
1359 .get("key")
1360 .map(|v| v.to_string())
1361 .unwrap(),
1362 "a[b]c"
1363 );
1364 }
1365
1366 #[test]
1367 fn test_parse_string_with_hash() {
1368 let input = r#"key = "a#b""#;
1369 let doc = parse(input).unwrap();
1370 assert_eq!(
1371 doc.blocks[0]
1372 .attributes
1373 .get("key")
1374 .map(|v| v.to_string())
1375 .unwrap(),
1376 "a#b"
1377 );
1378 }
1379
1380 #[test]
1381 fn test_parse_mixed_block_and_attrs() {
1382 let input = r#"
1383 outer {
1384 attr1 = 1
1385 inner "label" {
1386 attr2 = 2
1387 }
1388 attr3 = 3
1389 }
1390 "#;
1391 let doc = parse(input).unwrap();
1392 let outer = &doc.blocks[0];
1393 assert_eq!(outer.attributes.len(), 2); assert!(!outer.blocks.is_empty()); }
1396
1397 #[test]
1398 fn test_parse_block_starting_with_dot() {
1399 let input = r#"key = ".hidden""#;
1401 let doc = parse(input).unwrap();
1402 assert_eq!(
1403 doc.blocks[0]
1404 .attributes
1405 .get("key")
1406 .map(|v| v.to_string())
1407 .unwrap(),
1408 ".hidden"
1409 );
1410 }
1411
1412 #[test]
1413 fn test_parse_block_with_number_value() {
1414 let input = r#"key = 0"#;
1415 let doc = parse(input).unwrap();
1416 assert_eq!(
1417 doc.blocks[0]
1418 .attributes
1419 .get("key")
1420 .map(|v| v.to_string())
1421 .unwrap(),
1422 "0"
1423 );
1424 }
1425
1426 #[test]
1427 fn test_parse_bool_true_as_value() {
1428 let input = r#"key = true"#;
1429 let doc = parse(input).unwrap();
1430 assert_eq!(
1431 doc.blocks[0]
1432 .attributes
1433 .get("key")
1434 .map(|v| v.to_string())
1435 .unwrap(),
1436 "true"
1437 );
1438 }
1439
1440 #[test]
1441 fn test_parse_bool_false_as_value() {
1442 let input = r#"key = false"#;
1443 let doc = parse(input).unwrap();
1444 assert_eq!(
1445 doc.blocks[0]
1446 .attributes
1447 .get("key")
1448 .map(|v| v.to_string())
1449 .unwrap(),
1450 "false"
1451 );
1452 }
1453
1454 #[test]
1455 fn test_parse_null_as_value() {
1456 let input = r#"key = null"#;
1457 let doc = parse(input).unwrap();
1458 assert_eq!(
1459 doc.blocks[0]
1460 .attributes
1461 .get("key")
1462 .map(|v| v.to_string())
1463 .unwrap(),
1464 "null"
1465 );
1466 }
1467
1468 #[test]
1469 fn test_parse_string_with_unicode() {
1470 let input = r#"key = "hello 世界""#;
1471 let doc = parse(input).unwrap();
1472 assert_eq!(
1473 doc.blocks[0]
1474 .attributes
1475 .get("key")
1476 .map(|v| v.to_string())
1477 .unwrap(),
1478 "hello 世界"
1479 );
1480 }
1481
1482 #[test]
1483 fn test_parse_multiple_labels_different_types() {
1484 let input = r#"block "a" 'b' "c" {}"#;
1485 let doc = parse(input).unwrap();
1486 assert_eq!(doc.blocks[0].labels, vec!["a", "b", "c"]);
1487 }
1488
1489 #[test]
1490 fn test_parse_ident_value_followed_by_block() {
1491 let input = r#"block {
1493 type container
1494 name = "test"
1495 }"#;
1496 let doc = parse(input).unwrap();
1497 assert_eq!(doc.blocks[0].name, "block");
1498 }
1499
1500 #[test]
1501 fn test_parse_value_with_equals_in_string() {
1502 let input = r#"key = "a=b=c""#;
1504 let doc = parse(input).unwrap();
1505 assert_eq!(
1506 doc.blocks[0]
1507 .attributes
1508 .get("key")
1509 .map(|v| v.to_string())
1510 .unwrap(),
1511 "a=b=c"
1512 );
1513 }
1514
1515 #[test]
1516 fn test_parse_list_with_single_number() {
1517 let input = r#"nums = [42]"#;
1518 let doc = parse(input).unwrap();
1519 let attr = doc.blocks[0].attributes.get("nums").unwrap();
1520 match attr {
1521 Value::List(items) => assert_eq!(items.len(), 1),
1522 _ => panic!("Expected list"),
1523 }
1524 }
1525
1526 #[test]
1527 fn test_parse_list_with_trailing_newline() {
1528 let input = r#"
1529 nums = [
1530 1,
1531 2,
1532 ]
1533 "#;
1534 let doc = parse(input).unwrap();
1535 let attr = doc.blocks[0].attributes.get("nums").unwrap();
1536 match attr {
1537 Value::List(items) => assert_eq!(items.len(), 2),
1538 _ => panic!("Expected list"),
1539 }
1540 }
1541
1542 #[test]
1543 fn test_parse_attr_with_number_starting_with_dot() {
1544 let input = r#"key = ".5""#;
1546 let doc = parse(input).unwrap();
1547 assert_eq!(
1548 doc.blocks[0]
1549 .attributes
1550 .get("key")
1551 .map(|v| v.to_string())
1552 .unwrap(),
1553 ".5"
1554 );
1555 }
1556
1557 #[test]
1558 fn test_parse_eof_in_nested_block() {
1559 let input = r#"key = "unclosed"#;
1561 let result = parse(input);
1562 assert!(result.is_ok() || result.is_err());
1564 }
1565
1566 #[test]
1567 fn test_parse_block_with_comment_after_name() {
1568 let input = r#"
1569 block # comment
1570 key = "value"
1571 "#;
1572 let doc = parse(input).unwrap();
1573 assert_eq!(doc.blocks[0].name, "block");
1574 }
1575
1576 #[test]
1577 fn test_parse_float_with_leading_dot() {
1578 let input = r#"val = .123"#;
1579 let doc = parse(input).unwrap();
1580 let attr = doc.blocks[0].attributes.get("val").unwrap();
1581 match attr {
1582 Value::Number(n) => assert!((*n - 0.123).abs() < 0.001),
1583 _ => panic!("Expected number"),
1584 }
1585 }
1586
1587 #[test]
1588 fn test_parse_error_block_name_eof() {
1589 let input = r#"block";
1591 let result = parse(input);
1592 assert!(result.is_err());
1593 }
1594
1595 #[test]
1596 fn test_parse_error_eof_in_block() {
1597 // EOF after opening brace in block body
1598 let input = r#"block { attr"#;
1599 let result = parse(input);
1600 assert!(result.is_err());
1601 }
1602
1603 #[test]
1604 fn test_parse_eof_in_nested_block_graceful() {
1605 let input = r#"outer { inner"#;
1607 let result = parse(input);
1608 assert!(result.is_ok());
1610 }
1611
1612 #[test]
1613 fn test_parse_error_invalid_list_token() {
1614 let input = r#"val = [}"]"#;
1616 let result = parse(input);
1617 assert!(result.is_err());
1618 }
1619
1620 #[test]
1621 fn test_parse_ident_at_eof() {
1622 let input = r#"name"#;
1624 let doc = parse(input).unwrap();
1625 assert_eq!(doc.blocks.len(), 1);
1627 assert_eq!(doc.blocks[0].name, "name");
1628 }
1629
1630 #[test]
1631 fn test_parse_block_with_only_newline_in_body() {
1632 let input = r#"block {
1634 }"#;
1635 let doc = parse(input).unwrap();
1636 assert_eq!(doc.blocks.len(), 1);
1637 }
1638
1639 #[test]
1640 fn test_parse_error_number_as_block_name() {
1641 let input = r#"123"#;
1643 let result = parse(input);
1644 assert!(result.is_err());
1645 let err = result.unwrap_err();
1646 assert!(
1648 err.message.contains("Unexpected token") || err.message.contains("Expected block name")
1649 );
1650 }
1651
1652 #[test]
1653 fn test_parse_label_then_nested() {
1654 let input = r#"block "label" {
1656 key = "value"
1657 }"#;
1658 let doc = parse(input).unwrap();
1659 assert_eq!(doc.blocks.len(), 1);
1660 assert_eq!(doc.blocks[0].labels, vec!["label"]);
1661 }
1662
1663 #[test]
1664 fn test_parse_block_empty_string_label() {
1665 let input = r#"block "" {
1667 }"#;
1668 let doc = parse(input).unwrap();
1669 assert_eq!(doc.blocks.len(), 1);
1670 assert_eq!(doc.blocks[0].labels, vec![""]);
1671 }
1672
1673 #[test]
1674 fn test_parse_multiple_string_labels() {
1675 let input = r#"block "label1" "label2" {
1677 }"#;
1678 let doc = parse(input).unwrap();
1679 assert_eq!(doc.blocks.len(), 1);
1680 assert_eq!(doc.blocks[0].labels, vec!["label1", "label2"]);
1681 }
1682
1683 #[test]
1684 fn test_parse_string_then_brace_as_block() {
1685 let input = r#"provider "openai" {
1687 key = "value"
1688 }"#;
1689 let doc = parse(input).unwrap();
1690 assert_eq!(doc.blocks.len(), 1);
1691 assert_eq!(doc.blocks[0].name, "provider");
1692 }
1693
1694 #[test]
1695 fn test_parse_attribute_with_plus_equal() {
1696 let input = r#"vals += 1"#;
1698 let doc = parse(input).unwrap();
1699 assert_eq!(doc.blocks.len(), 1);
1700 }
1701
1702 #[test]
1703 fn test_parse_function_call_env() {
1704 let input = r#"api_key = env("API_KEY")"#;
1706 let doc = parse(input).unwrap();
1707 let attr = doc.blocks[0].attributes.get("api_key").unwrap();
1708 match attr {
1709 Value::Call(name, args) => {
1710 assert_eq!(name, "env");
1711 assert_eq!(args.len(), 1);
1712 assert_eq!(args[0], Value::String("API_KEY".to_string()));
1713 }
1714 _ => panic!("Expected Call value"),
1715 }
1716 }
1717
1718 #[test]
1719 fn test_parse_function_call_with_multiple_args() {
1720 let input = r#"url = concat("postgres://", host, ":", port)"#;
1722 let doc = parse(input).unwrap();
1723 let attr = doc.blocks[0].attributes.get("url").unwrap();
1724 match attr {
1725 Value::Call(name, args) => {
1726 assert_eq!(name, "concat");
1727 assert_eq!(args.len(), 4);
1728 }
1729 _ => panic!("Expected Call value"),
1730 }
1731 }
1732
1733 #[test]
1734 fn test_parse_function_call_with_nested() {
1735 let input = r#"path = concat(env("HOME"), "/", "file")"#;
1737 let doc = parse(input).unwrap();
1738 let attr = doc.blocks[0].attributes.get("path").unwrap();
1739 match attr {
1740 Value::Call(name, args) => {
1741 assert_eq!(name, "concat");
1742 assert_eq!(args.len(), 3);
1743 }
1744 _ => panic!("Expected Call value"),
1745 }
1746 }
1747
1748 #[test]
1749 fn test_parse_function_call_empty_args() {
1750 let input = r#"result = getenv()"#;
1752 let doc = parse(input).unwrap();
1753 let attr = doc.blocks[0].attributes.get("result").unwrap();
1754 match attr {
1755 Value::Call(name, args) => {
1756 assert_eq!(name, "getenv");
1757 assert!(args.is_empty());
1758 }
1759 _ => panic!("Expected Call value"),
1760 }
1761 }
1762
1763 #[test]
1764 fn test_parse_function_call_in_list() {
1765 let input = r#"paths = [env("PATH1"), env("PATH2")]"#;
1767 let doc = parse(input).unwrap();
1768 let attr = doc.blocks[0].attributes.get("paths").unwrap();
1769 match attr {
1770 Value::List(items) => {
1771 assert_eq!(items.len(), 2);
1772 }
1773 _ => panic!("Expected List value"),
1774 }
1775 }
1776
1777 #[test]
1778 fn test_parse_function_call_missing_paren() {
1779 let input = r#"val = env("API_KEY"#;
1781 let result = parse(input);
1782 assert!(result.is_err());
1783 }
1784
1785 #[test]
1786 fn test_parse_list_of_objects() {
1787 let input = r#"
1788 knowledge_bases = [
1789 { id = "WThXBKfN21eAxJOl3n1PA", name = "个人知识" },
1790 { id = "another_id", name = "another_name" }
1791 ]
1792 "#;
1793 let doc = parse(input).unwrap();
1794 let attr = doc.blocks[0].attributes.get("knowledge_bases").unwrap();
1795 match attr {
1796 Value::List(items) => {
1797 assert_eq!(items.len(), 2);
1798 match &items[0] {
1799 Value::Object(pairs) => {
1800 let map: HashMap<_, _> = pairs.iter().cloned().collect();
1801 assert_eq!(
1802 map.get("id"),
1803 Some(&Value::String("WThXBKfN21eAxJOl3n1PA".to_string()))
1804 );
1805 assert_eq!(
1806 map.get("name"),
1807 Some(&Value::String("个人知识".to_string()))
1808 );
1809 }
1810 _ => panic!("Expected Object value"),
1811 }
1812 }
1813 _ => panic!("Expected List value"),
1814 }
1815 }
1816
1817 #[test]
1818 fn test_parse_list_of_single_object() {
1819 let input = r#"items = [{ id = "1", name = "test" }]"#;
1820 let doc = parse(input).unwrap();
1821 let attr = doc.blocks[0].attributes.get("items").unwrap();
1822 match attr {
1823 Value::List(items) => assert_eq!(items.len(), 1),
1824 _ => panic!("Expected List value"),
1825 }
1826 }
1827}