From 4d4ef7595479b1dd75a59b52f31f8e5417daaf67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kat=20March=C3=A1n?= Date: Wed, 4 Dec 2024 13:01:02 -0800 Subject: [PATCH] feat(draft): 2.0.0-draft.6 support (#92) This just adds support for the new multiline string syntax. There's no other necessary changes to support draft.6. --- README.md | 3 + examples/ci.kdl | 4 +- src/lib.rs | 3 + src/v2_parser.rs | 87 ++++++++++++------- .../test_cases/expected_kdl/unicode_silly.kdl | 2 +- tests/test_cases/input/escaped_whitespace.kdl | 4 +- .../test_cases/input/multiline_raw_string.kdl | 4 +- .../input/multiline_raw_string_indented.kdl | 4 +- ...ng_non_matching_prefix_character_error.kdl | 4 +- ...string_non_matching_prefix_count_error.kdl | 4 +- .../multiline_raw_string_single_line_err.kdl | 1 + .../multiline_raw_string_single_quote_err.kdl | 5 ++ tests/test_cases/input/multiline_string.kdl | 4 +- .../input/multiline_string_indented.kdl | 4 +- ...ng_non_matching_prefix_character_error.kdl | 4 +- ...string_non_matching_prefix_count_error.kdl | 4 +- .../multiline_string_single_line_err.kdl | 1 + .../multiline_string_single_quote_err.kdl | 5 ++ tests/test_cases/input/raw_string_newline.kdl | 4 +- .../test_cases/input/slashdash_full_node.kdl | 4 +- .../{unicode_silly.kd => unicode_silly.kdl} | 0 21 files changed, 98 insertions(+), 57 deletions(-) create mode 100644 tests/test_cases/input/multiline_raw_string_single_line_err.kdl create mode 100644 tests/test_cases/input/multiline_raw_string_single_quote_err.kdl create mode 100644 tests/test_cases/input/multiline_string_single_line_err.kdl create mode 100644 tests/test_cases/input/multiline_string_single_quote_err.kdl rename tests/test_cases/input/{unicode_silly.kd => unicode_silly.kdl} (100%) diff --git a/README.md b/README.md index b44866e..e44ab7e 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,9 @@ check out [`knuffel`](https://crates.io/crates/knuffel) or [`kaydle`](https://crates.io/crates/kaydle) instead for serde (or serde-like) parsing. +This crate supports parsing [KDL +2.0.0-draft.6](https://github.com/kdl-org/kdl/releases/tag/2.0.0-draft.6) + ### Example ```rust diff --git a/examples/ci.kdl b/examples/ci.kdl index 1e000aa..84fdaf0 100644 --- a/examples/ci.kdl +++ b/examples/ci.kdl @@ -42,11 +42,11 @@ jobs { } step Clippy { run cargo clippy --all -- -D warnings } step "Run tests" { run cargo test --all --verbose } - step "Other Stuff" run=" + step "Other Stuff" run=""" echo foo echo bar echo baz - " + """ } } } diff --git a/src/lib.rs b/src/lib.rs index 8ae8e86..e9affe2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,9 @@ //! [`kaydle`](https://crates.io/crates/kaydle) instead for serde (or //! serde-like) parsing. //! +//! This crate supports parsing [KDL +//! 2.0.0-draft.6](https://github.com/kdl-org/kdl/releases/tag/2.0.0-draft.6) +//! //! ## Example //! //! ```rust diff --git a/src/v2_parser.rs b/src/v2_parser.rs index c0a1754..ca7e80b 100644 --- a/src/v2_parser.rs +++ b/src/v2_parser.rs @@ -766,13 +766,13 @@ fn equals_sign(input: &mut Input<'_>) -> PResult<()> { } /// ```text -/// quoted-string := '"' (single-line-string-body | newline multi-line-string-body newline unicode-space*) '"' +/// quoted-string := '"' single-line-string-body '"' | '"""' newline multi-line-string-body newline unicode-space*) '"""' /// single-line-string-body := (string-character - newline)* /// multi-line-string-body := string-character* /// ``` fn quoted_string<'s>(input: &mut Input<'s>) -> PResult> { - "\"".parse_next(input)?; - let is_multiline = opt(newline).parse_next(input)?.is_some(); + let quotes = alt((("\"\"\"", newline).take(), "\"")).parse_next(input)?; + let is_multiline = quotes.len() > 1; let ml_prefix: Option = if is_multiline { Some( peek(preceded( @@ -782,10 +782,13 @@ fn quoted_string<'s>(input: &mut Input<'s>) -> PResult> { repeat(0.., (not(newline), opt(ws_escape), string_char)).map(|()| ()), newline, ), - peek(terminated(repeat(0.., unicode_space).map(|()| ()), "\"")), + peek(terminated( + repeat(0.., unicode_space).map(|()| ()), + "\"\"\"", + )), ) .map(|((), ())| ()), - terminated(repeat(0.., unicode_space).map(|()| ()).take(), "\""), + terminated(repeat(0.., unicode_space).map(|()| ()).take(), "\"\"\""), )) .parse_next(input)? .to_string(), @@ -814,7 +817,7 @@ fn quoted_string<'s>(input: &mut Input<'s>) -> PResult> { ( &prefix[..], repeat(0.., unicode_space).map(|()| ()).take(), - peek("\""), + peek("\"\"\""), ), ) .map(|(s, _): (Vec, (_, _, _))| { @@ -836,9 +839,12 @@ fn quoted_string<'s>(input: &mut Input<'s>) -> PResult> { .resume_after(quoted_string_badval) .parse_next(input)? }; - cut_err("\"") - .context(lbl("closing quote")) - .parse_next(input)?; + let closing_quotes = if is_multiline { + "\"\"\"".context(lbl("multiline string closing quotes")) + } else { + "\"".context(lbl("string closing quote")) + }; + cut_err(closing_quotes).parse_next(input)?; Ok(body.map(KdlValue::String)) } @@ -903,13 +909,15 @@ fn escaped_char(input: &mut Input<'_>) -> PResult { } /// `raw-string := '#' raw-string-quotes '#' | '#' raw-string '#'` -/// `raw-string-quotes := '"' (single-line-raw-string-body | newline multi-line-raw-string-body newline unicode-space*) '"'` +/// `raw-string-quotes := '"' single-line-raw-string-body '"' | '"""' newline multi-line-raw-string-body newline unicode-space*) '"""'` /// `single-line-raw-string-body := (unicode - newline - disallowed-literal-code-points)*` /// `multi-line-raw-string-body := (unicode - disallowed-literal-code-points)` fn raw_string(input: &mut Input<'_>) -> PResult> { let hashes: String = repeat(1.., "#").parse_next(input)?; - "\"".parse_next(input)?; - let is_multiline = opt(newline).parse_next(input)?.is_some(); + let quotes = alt((("\"\"\"", newline).take(), "\"")).parse_next(input)?; + let is_multiline = quotes.len() > 1; + dbg!("es); + dbg!(is_multiline); let ml_prefix: Option = if is_multiline { Some( peek(preceded( @@ -921,7 +929,7 @@ fn raw_string(input: &mut Input<'_>) -> PResult> { ( not(newline), not(disallowed_unicode), - not(("\"", &hashes[..])), + not(("\"\"\"", &hashes[..])), any, ), ) @@ -930,13 +938,13 @@ fn raw_string(input: &mut Input<'_>) -> PResult> { ), peek(terminated( repeat(0.., unicode_space).map(|()| ()), - ("\"", &hashes[..]), + ("\"\"\"", &hashes[..]), )), ) .map(|((), ())| ()), terminated( repeat(0.., unicode_space).map(|()| ()).take(), - ("\"", &hashes[..]), + ("\"\"\"", &hashes[..]), ), )) .parse_next(input)? @@ -945,6 +953,7 @@ fn raw_string(input: &mut Input<'_>) -> PResult> { } else { None }; + dbg!(&ml_prefix); let body: Option = if let Some(prefix) = ml_prefix { repeat_till( 0.., @@ -955,7 +964,7 @@ fn raw_string(input: &mut Input<'_>) -> PResult> { newline.take().map(|_| "\n".to_string()), repeat_till( 0.., - (not(newline), not(("\"", &hashes[..])), any) + (not(newline), not(("\"\"\"", &hashes[..])), any) .map(|((), (), _)| ()) .take(), newline, @@ -968,7 +977,7 @@ fn raw_string(input: &mut Input<'_>) -> PResult> { ( &prefix[..], repeat(0.., unicode_space).map(|()| ()).take(), - peek(("\"", &hashes[..])), + peek(("\"\"\"", &hashes[..])), ), ) .map(|(s, _): (Vec, (_, _, _))| { @@ -996,9 +1005,12 @@ fn raw_string(input: &mut Input<'_>) -> PResult> { .resume_after(raw_string_badval) .parse_next(input)? }; - cut_err(("\"", &hashes[..])) - .context(lbl("closing quote")) - .parse_next(input)?; + let closing_quotes = if is_multiline { + "\"\"\"".context(lbl("multiline raw string closing quotes")) + } else { + "\"".context(lbl("raw string closing quotes")) + }; + cut_err((closing_quotes, &hashes[..])).parse_next(input)?; Ok(body.map(KdlValue::String)) } @@ -1044,40 +1056,46 @@ mod string_tests { #[test] fn multiline_quoted_string() { assert_eq!( - string.parse(new_input("\"\nfoo\nbar\nbaz\n\"")).unwrap(), + string + .parse(new_input("\"\"\"\nfoo\nbar\nbaz\n\"\"\"")) + .unwrap(), Some(KdlValue::String("foo\nbar\nbaz".into())) ); assert_eq!( string - .parse(new_input("\"\n foo\n bar\n baz\n \"")) + .parse(new_input("\"\"\"\n foo\n bar\n baz\n \"\"\"")) .unwrap(), Some(KdlValue::String("foo\n bar\nbaz".into())) ); assert_eq!( - string.parse(new_input("\"\nfoo\r\nbar\nbaz\n\"")).unwrap(), + string + .parse(new_input("\"\"\"\nfoo\r\nbar\nbaz\n\"\"\"")) + .unwrap(), Some(KdlValue::String("foo\nbar\nbaz".into())) ); assert_eq!( string - .parse(new_input("\"\n foo\n bar\n baz\n \"")) + .parse(new_input("\"\"\"\n foo\n bar\n baz\n \"\"\"")) .unwrap(), Some(KdlValue::String("foo\n bar\n baz".into())) ); assert_eq!( string - .parse(new_input("\"\n \\ foo\n \\ bar\n \\ baz\n \"")) + .parse(new_input( + "\"\"\"\n \\ foo\n \\ bar\n \\ baz\n \"\"\"" + )) .unwrap(), Some(KdlValue::String("foo\n bar\n baz".into())) ); assert_eq!( string - .parse(new_input("\"\n\n string\t\n \"")) + .parse(new_input("\"\"\"\n\n string\t\n \"\"\"")) .unwrap(), Some(KdlValue::String("\nstring\t".into())), "Empty line without any indentation" ); assert!(string - .parse(new_input("\"\nfoo\n bar\n baz\n \"")) + .parse(new_input("\"\"\"\nfoo\n bar\n baz\n \"\"\"")) .is_err()); } @@ -1092,30 +1110,35 @@ mod string_tests { #[test] fn multiline_raw_string() { assert_eq!( - string.parse(new_input("#\"\nfoo\nbar\nbaz\n\"#")).unwrap(), + string + .parse(new_input("#\"\"\"\nfoo\nbar\nbaz\n\"\"\"#")) + .unwrap(), Some(KdlValue::String("foo\nbar\nbaz".into())) ); assert_eq!( string - .parse(new_input("#\"\nfoo\r\nbar\nbaz\n\"#")) + .parse(new_input("#\"\"\"\nfoo\r\nbar\nbaz\n\"\"\"#")) .unwrap(), Some(KdlValue::String("foo\nbar\nbaz".into())) ); assert_eq!( string - .parse(new_input("##\"\n foo\n bar\n baz\n \"##")) + .parse(new_input("##\"\"\"\n foo\n bar\n baz\n \"\"\"##")) .unwrap(), Some(KdlValue::String("foo\n bar\nbaz".into())) ); assert_eq!( string - .parse(new_input("#\"\n foo\n \\nbar\n baz\n \"#")) + .parse(new_input("#\"\"\"\n foo\n \\nbar\n baz\n \"\"\"#")) .unwrap(), Some(KdlValue::String("foo\n \\nbar\n baz".into())) ); assert!(string - .parse(new_input("#\"\nfoo\n bar\n baz\n \"#")) + .parse(new_input("#\"\"\"\nfoo\n bar\n baz\n \"\"\"#")) .is_err()); + + assert!(string.parse(new_input("#\"\nfoo\nbar\nbaz\n\"#")).is_err()); + assert!(string.parse(new_input("\"\nfoo\nbar\nbaz\n\"")).is_err()); } #[test] diff --git a/tests/test_cases/expected_kdl/unicode_silly.kdl b/tests/test_cases/expected_kdl/unicode_silly.kdl index 5fa566d..39dfdb9 100644 --- a/tests/test_cases/expected_kdl/unicode_silly.kdl +++ b/tests/test_cases/expected_kdl/unicode_silly.kdl @@ -1 +1 @@ -ノード お名前=ฅ^•ﻌ•^ฅ +ノード お名前=ฅ^•ﻌ•^ฅ diff --git a/tests/test_cases/input/escaped_whitespace.kdl b/tests/test_cases/input/escaped_whitespace.kdl index 797784a..620c6df 100644 --- a/tests/test_cases/input/escaped_whitespace.kdl +++ b/tests/test_cases/input/escaped_whitespace.kdl @@ -1,10 +1,10 @@ // All of these strings are the same node \ "Hello\n\tWorld" \ - " + """ Hello World - " \ + """ \ "Hello\n\ \tWorld" \ "Hello\n\ \tWorld" \ diff --git a/tests/test_cases/input/multiline_raw_string.kdl b/tests/test_cases/input/multiline_raw_string.kdl index eaa212e..59d190b 100644 --- a/tests/test_cases/input/multiline_raw_string.kdl +++ b/tests/test_cases/input/multiline_raw_string.kdl @@ -1,5 +1,5 @@ -node #" +node #""" hey everyone how goes? -"# +"""# diff --git a/tests/test_cases/input/multiline_raw_string_indented.kdl b/tests/test_cases/input/multiline_raw_string_indented.kdl index 67ef76d..427b5a9 100644 --- a/tests/test_cases/input/multiline_raw_string_indented.kdl +++ b/tests/test_cases/input/multiline_raw_string_indented.kdl @@ -1,5 +1,5 @@ -node #" +node #""" hey everyone how goes? - "# + """# diff --git a/tests/test_cases/input/multiline_raw_string_non_matching_prefix_character_error.kdl b/tests/test_cases/input/multiline_raw_string_non_matching_prefix_character_error.kdl index c5650e9..bec4d2e 100644 --- a/tests/test_cases/input/multiline_raw_string_non_matching_prefix_character_error.kdl +++ b/tests/test_cases/input/multiline_raw_string_non_matching_prefix_character_error.kdl @@ -1,5 +1,5 @@ -node #" +node #""" hey everyone how goes? - "# + """# diff --git a/tests/test_cases/input/multiline_raw_string_non_matching_prefix_count_error.kdl b/tests/test_cases/input/multiline_raw_string_non_matching_prefix_count_error.kdl index c0f4f56..06e7a05 100644 --- a/tests/test_cases/input/multiline_raw_string_non_matching_prefix_count_error.kdl +++ b/tests/test_cases/input/multiline_raw_string_non_matching_prefix_count_error.kdl @@ -1,5 +1,5 @@ -node #" +node #""" hey everyone how goes? - "# + """# diff --git a/tests/test_cases/input/multiline_raw_string_single_line_err.kdl b/tests/test_cases/input/multiline_raw_string_single_line_err.kdl new file mode 100644 index 0000000..e542c02 --- /dev/null +++ b/tests/test_cases/input/multiline_raw_string_single_line_err.kdl @@ -0,0 +1 @@ +node #"""one line"""# \ No newline at end of file diff --git a/tests/test_cases/input/multiline_raw_string_single_quote_err.kdl b/tests/test_cases/input/multiline_raw_string_single_quote_err.kdl new file mode 100644 index 0000000..eaa212e --- /dev/null +++ b/tests/test_cases/input/multiline_raw_string_single_quote_err.kdl @@ -0,0 +1,5 @@ +node #" +hey +everyone +how goes? +"# diff --git a/tests/test_cases/input/multiline_string.kdl b/tests/test_cases/input/multiline_string.kdl index e3a6cc1..4b27edf 100644 --- a/tests/test_cases/input/multiline_string.kdl +++ b/tests/test_cases/input/multiline_string.kdl @@ -1,5 +1,5 @@ -node " +node """ hey everyone how goes? -" +""" diff --git a/tests/test_cases/input/multiline_string_indented.kdl b/tests/test_cases/input/multiline_string_indented.kdl index ce9ca16..bc0a10b 100644 --- a/tests/test_cases/input/multiline_string_indented.kdl +++ b/tests/test_cases/input/multiline_string_indented.kdl @@ -1,5 +1,5 @@ -node " +node """ hey everyone how goes? - " + """ diff --git a/tests/test_cases/input/multiline_string_non_matching_prefix_character_error.kdl b/tests/test_cases/input/multiline_string_non_matching_prefix_character_error.kdl index 1c2ca85..e46e8d1 100644 --- a/tests/test_cases/input/multiline_string_non_matching_prefix_character_error.kdl +++ b/tests/test_cases/input/multiline_string_non_matching_prefix_character_error.kdl @@ -1,5 +1,5 @@ -node " +node """ hey everyone how goes? - " + """ diff --git a/tests/test_cases/input/multiline_string_non_matching_prefix_count_error.kdl b/tests/test_cases/input/multiline_string_non_matching_prefix_count_error.kdl index 86a2867..9cc70b1 100644 --- a/tests/test_cases/input/multiline_string_non_matching_prefix_count_error.kdl +++ b/tests/test_cases/input/multiline_string_non_matching_prefix_count_error.kdl @@ -1,5 +1,5 @@ -node " +node """ hey everyone how goes? - " + """ diff --git a/tests/test_cases/input/multiline_string_single_line_err.kdl b/tests/test_cases/input/multiline_string_single_line_err.kdl new file mode 100644 index 0000000..ee36794 --- /dev/null +++ b/tests/test_cases/input/multiline_string_single_line_err.kdl @@ -0,0 +1 @@ +node """one line""" \ No newline at end of file diff --git a/tests/test_cases/input/multiline_string_single_quote_err.kdl b/tests/test_cases/input/multiline_string_single_quote_err.kdl new file mode 100644 index 0000000..e3a6cc1 --- /dev/null +++ b/tests/test_cases/input/multiline_string_single_quote_err.kdl @@ -0,0 +1,5 @@ +node " +hey +everyone +how goes? +" diff --git a/tests/test_cases/input/raw_string_newline.kdl b/tests/test_cases/input/raw_string_newline.kdl index 0cc85c0..12304d9 100644 --- a/tests/test_cases/input/raw_string_newline.kdl +++ b/tests/test_cases/input/raw_string_newline.kdl @@ -1,4 +1,4 @@ -node #" +node #""" hello world -"# +"""# diff --git a/tests/test_cases/input/slashdash_full_node.kdl b/tests/test_cases/input/slashdash_full_node.kdl index 4df7b55..4ef041b 100644 --- a/tests/test_cases/input/slashdash_full_node.kdl +++ b/tests/test_cases/input/slashdash_full_node.kdl @@ -1,3 +1,3 @@ -/- node 1.0 "a" b=" +/- node 1.0 "a" b=""" b -" +""" diff --git a/tests/test_cases/input/unicode_silly.kd b/tests/test_cases/input/unicode_silly.kdl similarity index 100% rename from tests/test_cases/input/unicode_silly.kd rename to tests/test_cases/input/unicode_silly.kdl