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.
This commit is contained in:
Kat Marchán 2024-12-04 13:01:02 -08:00
parent 71bd07b57d
commit 4d4ef75954
No known key found for this signature in database
GPG Key ID: AEB529C08A3C7E9E
21 changed files with 101 additions and 60 deletions

View File

@ -15,6 +15,9 @@ check out [`knuffel`](https://crates.io/crates/knuffel) or
[`kaydle`](https://crates.io/crates/kaydle) instead for serde (or [`kaydle`](https://crates.io/crates/kaydle) instead for serde (or
serde-like) parsing. 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 ### Example
```rust ```rust

View File

@ -42,11 +42,11 @@ jobs {
} }
step Clippy { run cargo clippy --all -- -D warnings } step Clippy { run cargo clippy --all -- -D warnings }
step "Run tests" { run cargo test --all --verbose } step "Run tests" { run cargo test --all --verbose }
step "Other Stuff" run=" step "Other Stuff" run="""
echo foo echo foo
echo bar echo bar
echo baz echo baz
" """
} }
} }
} }

View File

@ -13,6 +13,9 @@
//! [`kaydle`](https://crates.io/crates/kaydle) instead for serde (or //! [`kaydle`](https://crates.io/crates/kaydle) instead for serde (or
//! serde-like) parsing. //! 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 //! ## Example
//! //!
//! ```rust //! ```rust

View File

@ -766,13 +766,13 @@ fn equals_sign(input: &mut Input<'_>) -> PResult<()> {
} }
/// ```text /// ```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)* /// single-line-string-body := (string-character - newline)*
/// multi-line-string-body := string-character* /// multi-line-string-body := string-character*
/// ``` /// ```
fn quoted_string<'s>(input: &mut Input<'s>) -> PResult<Option<KdlValue>> { fn quoted_string<'s>(input: &mut Input<'s>) -> PResult<Option<KdlValue>> {
"\"".parse_next(input)?; let quotes = alt((("\"\"\"", newline).take(), "\"")).parse_next(input)?;
let is_multiline = opt(newline).parse_next(input)?.is_some(); let is_multiline = quotes.len() > 1;
let ml_prefix: Option<String> = if is_multiline { let ml_prefix: Option<String> = if is_multiline {
Some( Some(
peek(preceded( peek(preceded(
@ -782,10 +782,13 @@ fn quoted_string<'s>(input: &mut Input<'s>) -> PResult<Option<KdlValue>> {
repeat(0.., (not(newline), opt(ws_escape), string_char)).map(|()| ()), repeat(0.., (not(newline), opt(ws_escape), string_char)).map(|()| ()),
newline, newline,
), ),
peek(terminated(repeat(0.., unicode_space).map(|()| ()), "\"")), peek(terminated(
repeat(0.., unicode_space).map(|()| ()),
"\"\"\"",
)),
) )
.map(|((), ())| ()), .map(|((), ())| ()),
terminated(repeat(0.., unicode_space).map(|()| ()).take(), "\""), terminated(repeat(0.., unicode_space).map(|()| ()).take(), "\"\"\""),
)) ))
.parse_next(input)? .parse_next(input)?
.to_string(), .to_string(),
@ -814,7 +817,7 @@ fn quoted_string<'s>(input: &mut Input<'s>) -> PResult<Option<KdlValue>> {
( (
&prefix[..], &prefix[..],
repeat(0.., unicode_space).map(|()| ()).take(), repeat(0.., unicode_space).map(|()| ()).take(),
peek("\""), peek("\"\"\""),
), ),
) )
.map(|(s, _): (Vec<String>, (_, _, _))| { .map(|(s, _): (Vec<String>, (_, _, _))| {
@ -836,9 +839,12 @@ fn quoted_string<'s>(input: &mut Input<'s>) -> PResult<Option<KdlValue>> {
.resume_after(quoted_string_badval) .resume_after(quoted_string_badval)
.parse_next(input)? .parse_next(input)?
}; };
cut_err("\"") let closing_quotes = if is_multiline {
.context(lbl("closing quote")) "\"\"\"".context(lbl("multiline string closing quotes"))
.parse_next(input)?; } else {
"\"".context(lbl("string closing quote"))
};
cut_err(closing_quotes).parse_next(input)?;
Ok(body.map(KdlValue::String)) Ok(body.map(KdlValue::String))
} }
@ -903,13 +909,15 @@ fn escaped_char(input: &mut Input<'_>) -> PResult<char> {
} }
/// `raw-string := '#' raw-string-quotes '#' | '#' raw-string '#'` /// `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)*` /// `single-line-raw-string-body := (unicode - newline - disallowed-literal-code-points)*`
/// `multi-line-raw-string-body := (unicode - disallowed-literal-code-points)` /// `multi-line-raw-string-body := (unicode - disallowed-literal-code-points)`
fn raw_string(input: &mut Input<'_>) -> PResult<Option<KdlValue>> { fn raw_string(input: &mut Input<'_>) -> PResult<Option<KdlValue>> {
let hashes: String = repeat(1.., "#").parse_next(input)?; let hashes: String = repeat(1.., "#").parse_next(input)?;
"\"".parse_next(input)?; let quotes = alt((("\"\"\"", newline).take(), "\"")).parse_next(input)?;
let is_multiline = opt(newline).parse_next(input)?.is_some(); let is_multiline = quotes.len() > 1;
dbg!(&quotes);
dbg!(is_multiline);
let ml_prefix: Option<String> = if is_multiline { let ml_prefix: Option<String> = if is_multiline {
Some( Some(
peek(preceded( peek(preceded(
@ -921,7 +929,7 @@ fn raw_string(input: &mut Input<'_>) -> PResult<Option<KdlValue>> {
( (
not(newline), not(newline),
not(disallowed_unicode), not(disallowed_unicode),
not(("\"", &hashes[..])), not(("\"\"\"", &hashes[..])),
any, any,
), ),
) )
@ -930,13 +938,13 @@ fn raw_string(input: &mut Input<'_>) -> PResult<Option<KdlValue>> {
), ),
peek(terminated( peek(terminated(
repeat(0.., unicode_space).map(|()| ()), repeat(0.., unicode_space).map(|()| ()),
("\"", &hashes[..]), ("\"\"\"", &hashes[..]),
)), )),
) )
.map(|((), ())| ()), .map(|((), ())| ()),
terminated( terminated(
repeat(0.., unicode_space).map(|()| ()).take(), repeat(0.., unicode_space).map(|()| ()).take(),
("\"", &hashes[..]), ("\"\"\"", &hashes[..]),
), ),
)) ))
.parse_next(input)? .parse_next(input)?
@ -945,6 +953,7 @@ fn raw_string(input: &mut Input<'_>) -> PResult<Option<KdlValue>> {
} else { } else {
None None
}; };
dbg!(&ml_prefix);
let body: Option<String> = if let Some(prefix) = ml_prefix { let body: Option<String> = if let Some(prefix) = ml_prefix {
repeat_till( repeat_till(
0.., 0..,
@ -955,7 +964,7 @@ fn raw_string(input: &mut Input<'_>) -> PResult<Option<KdlValue>> {
newline.take().map(|_| "\n".to_string()), newline.take().map(|_| "\n".to_string()),
repeat_till( repeat_till(
0.., 0..,
(not(newline), not(("\"", &hashes[..])), any) (not(newline), not(("\"\"\"", &hashes[..])), any)
.map(|((), (), _)| ()) .map(|((), (), _)| ())
.take(), .take(),
newline, newline,
@ -968,7 +977,7 @@ fn raw_string(input: &mut Input<'_>) -> PResult<Option<KdlValue>> {
( (
&prefix[..], &prefix[..],
repeat(0.., unicode_space).map(|()| ()).take(), repeat(0.., unicode_space).map(|()| ()).take(),
peek(("\"", &hashes[..])), peek(("\"\"\"", &hashes[..])),
), ),
) )
.map(|(s, _): (Vec<String>, (_, _, _))| { .map(|(s, _): (Vec<String>, (_, _, _))| {
@ -996,9 +1005,12 @@ fn raw_string(input: &mut Input<'_>) -> PResult<Option<KdlValue>> {
.resume_after(raw_string_badval) .resume_after(raw_string_badval)
.parse_next(input)? .parse_next(input)?
}; };
cut_err(("\"", &hashes[..])) let closing_quotes = if is_multiline {
.context(lbl("closing quote")) "\"\"\"".context(lbl("multiline raw string closing quotes"))
.parse_next(input)?; } else {
"\"".context(lbl("raw string closing quotes"))
};
cut_err((closing_quotes, &hashes[..])).parse_next(input)?;
Ok(body.map(KdlValue::String)) Ok(body.map(KdlValue::String))
} }
@ -1044,40 +1056,46 @@ mod string_tests {
#[test] #[test]
fn multiline_quoted_string() { fn multiline_quoted_string() {
assert_eq!( 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())) Some(KdlValue::String("foo\nbar\nbaz".into()))
); );
assert_eq!( assert_eq!(
string string
.parse(new_input("\"\n foo\n bar\n baz\n \"")) .parse(new_input("\"\"\"\n foo\n bar\n baz\n \"\"\""))
.unwrap(), .unwrap(),
Some(KdlValue::String("foo\n bar\nbaz".into())) Some(KdlValue::String("foo\n bar\nbaz".into()))
); );
assert_eq!( 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())) Some(KdlValue::String("foo\nbar\nbaz".into()))
); );
assert_eq!( assert_eq!(
string string
.parse(new_input("\"\n foo\n bar\n baz\n \"")) .parse(new_input("\"\"\"\n foo\n bar\n baz\n \"\"\""))
.unwrap(), .unwrap(),
Some(KdlValue::String("foo\n bar\n baz".into())) Some(KdlValue::String("foo\n bar\n baz".into()))
); );
assert_eq!( assert_eq!(
string string
.parse(new_input("\"\n \\ foo\n \\ bar\n \\ baz\n \"")) .parse(new_input(
"\"\"\"\n \\ foo\n \\ bar\n \\ baz\n \"\"\""
))
.unwrap(), .unwrap(),
Some(KdlValue::String("foo\n bar\n baz".into())) Some(KdlValue::String("foo\n bar\n baz".into()))
); );
assert_eq!( assert_eq!(
string string
.parse(new_input("\"\n\n string\t\n \"")) .parse(new_input("\"\"\"\n\n string\t\n \"\"\""))
.unwrap(), .unwrap(),
Some(KdlValue::String("\nstring\t".into())), Some(KdlValue::String("\nstring\t".into())),
"Empty line without any indentation" "Empty line without any indentation"
); );
assert!(string assert!(string
.parse(new_input("\"\nfoo\n bar\n baz\n \"")) .parse(new_input("\"\"\"\nfoo\n bar\n baz\n \"\"\""))
.is_err()); .is_err());
} }
@ -1091,31 +1109,36 @@ mod string_tests {
#[test] #[test]
fn multiline_raw_string() { fn multiline_raw_string() {
assert_eq!(
string.parse(new_input("#\"\nfoo\nbar\nbaz\n\"#")).unwrap(),
Some(KdlValue::String("foo\nbar\nbaz".into()))
);
assert_eq!( assert_eq!(
string string
.parse(new_input("#\"\nfoo\r\nbar\nbaz\n\"#")) .parse(new_input("#\"\"\"\nfoo\nbar\nbaz\n\"\"\"#"))
.unwrap(), .unwrap(),
Some(KdlValue::String("foo\nbar\nbaz".into())) Some(KdlValue::String("foo\nbar\nbaz".into()))
); );
assert_eq!( assert_eq!(
string string
.parse(new_input("##\"\n foo\n bar\n baz\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 \"\"\"##"))
.unwrap(), .unwrap(),
Some(KdlValue::String("foo\n bar\nbaz".into())) Some(KdlValue::String("foo\n bar\nbaz".into()))
); );
assert_eq!( assert_eq!(
string string
.parse(new_input("#\"\n foo\n \\nbar\n baz\n \"#")) .parse(new_input("#\"\"\"\n foo\n \\nbar\n baz\n \"\"\"#"))
.unwrap(), .unwrap(),
Some(KdlValue::String("foo\n \\nbar\n baz".into())) Some(KdlValue::String("foo\n \\nbar\n baz".into()))
); );
assert!(string assert!(string
.parse(new_input("#\"\nfoo\n bar\n baz\n \"#")) .parse(new_input("#\"\"\"\nfoo\n bar\n baz\n \"\"\"#"))
.is_err()); .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] #[test]

View File

@ -1 +1 @@
ノード お名前=ฅ^•ﻌ•^ฅ ノード お名前=ฅ^•ﻌ•^ฅ

View File

@ -1,10 +1,10 @@
// All of these strings are the same // All of these strings are the same
node \ node \
"Hello\n\tWorld" \ "Hello\n\tWorld" \
" """
Hello Hello
World World
" \ """ \
"Hello\n\ \tWorld" \ "Hello\n\ \tWorld" \
"Hello\n\ "Hello\n\
\tWorld" \ \tWorld" \

View File

@ -1,5 +1,5 @@
node #" node #"""
hey hey
everyone everyone
how goes? how goes?
"# """#

View File

@ -1,5 +1,5 @@
node #" node #"""
hey hey
everyone everyone
how goes? how goes?
"# """#

View File

@ -1,5 +1,5 @@
node #" node #"""
hey hey
everyone everyone
how goes? how goes?
"# """#

View File

@ -1,5 +1,5 @@
node #" node #"""
hey hey
everyone everyone
how goes? how goes?
"# """#

View File

@ -0,0 +1 @@
node #"""one line"""#

View File

@ -0,0 +1,5 @@
node #"
hey
everyone
how goes?
"#

View File

@ -1,5 +1,5 @@
node " node """
hey hey
everyone everyone
how goes? how goes?
" """

View File

@ -1,5 +1,5 @@
node " node """
hey hey
everyone everyone
how goes? how goes?
" """

View File

@ -1,5 +1,5 @@
node " node """
hey hey
everyone everyone
how goes? how goes?
" """

View File

@ -1,5 +1,5 @@
node " node """
hey hey
everyone everyone
how goes? how goes?
" """

View File

@ -0,0 +1 @@
node """one line"""

View File

@ -0,0 +1,5 @@
node "
hey
everyone
how goes?
"

View File

@ -1,4 +1,4 @@
node #" node #"""
hello hello
world world
"# """#

View File

@ -1,3 +1,3 @@
/- node 1.0 "a" b=" /- node 1.0 "a" b="""
b b
" """