diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 0f3c0fb..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: CI - -on: [push, pull_request] - -env: - RUSTFLAGS: -Dwarnings - -jobs: - fmt_and_docs: - name: Check fmt & build docs - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - components: rustfmt - override: true - - name: rustfmt - run: cargo fmt --all -- --check - - name: docs - run: cargo doc --no-deps - - build_and_test: - name: Build & Test - runs-on: ${{ matrix.os }} - strategy: - matrix: - rust: [1.46.0, stable] - os: [ubuntu-latest, macOS-latest, windows-latest] - - steps: - - uses: actions/checkout@v1 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - components: clippy - override: true - - name: Clippy - run: cargo clippy --all -- -D warnings - - name: Run tests - run: cargo test --all --verbose diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index a49c92f..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "kdl" -version = "0.0.0" -description = "kat's document language" -authors = ["Kat Marchán "] -license-file = "LICENSE.md" -edition = "2018" - -[dependencies] -nom = "6.0.1" -thiserror = "1.0.22" diff --git a/LICENSE.md b/LICENSE.md deleted file mode 100644 index c24143a..0000000 --- a/LICENSE.md +++ /dev/null @@ -1,71 +0,0 @@ -# The Parity Public License 7.0.0 - -Contributor: Kat Marchán - -Source Code: https://github.com/zkat/kdl - -## Purpose - -This license allows you to use and share this software for free, but you have to share software that builds on it alike. - -## Agreement - -In order to receive this license, you have to agree to its rules. Those rules are both obligations under that agreement and conditions to your license. Don't do anything with this software that triggers a rule you can't or won't follow. - -## Notices - -Make sure everyone who gets a copy of any part of this software from you, with or without changes, also gets the text of this license and the contributor and source code lines above. - -## Copyleft - -[Contribute](#contribute) software you develop, operate, or analyze with this software, including changes or additions to this software. When in doubt, [contribute](#contribute). - -## Prototypes - -You don't have to [contribute](#contribute) any change, addition, or other software that meets all these criteria: - -1. You don't use it for more than thirty days. - -2. You don't share it outside the team developing it, other than for non-production user testing. - -3. You don't develop, operate, or analyze other software with it for anyone outside the team developing it. - -## Reverse Engineering - -You may use this software to operate and analyze software you can't [contribute](#contribute) in order to develop alternatives you can and do [contribute](#contribute). - -## Contribute - -To [contribute](#contribute) software: - -1. Publish all source code for the software in the preferred form for making changes through a freely accessible distribution system widely used for similar source code so the contributor and others can find and copy it. - -2. Make sure every part of the source code is available under this license or another license that allows everything this license does, such as [the Blue Oak Model License 1.0.0](https://blueoakcouncil.org/license/1.0.0), [the Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0.html), [the MIT license](https://spdx.org/licenses/MIT.html), or [the two-clause BSD license](https://spdx.org/licenses/BSD-2-Clause.html). - -3. Take these steps within thirty days. - -4. Note that this license does _not_ allow you to change the license terms for this software. You must follow [Notices](#notices). - -## Excuse - -You're excused for unknowingly breaking [Copyleft](#copyleft) if you [contribute](#contribute) as required, or stop doing anything requiring this license, within thirty days of learning you broke the rule. You're excused for unknowingly breaking [Notices](#notices) if you take all practical steps to comply within thirty days of learning you broke the rule. - -## Defense - -Don't make any legal claim against anyone accusing this software, with or without changes, alone or with other technology, of infringing any patent. - -## Copyright - -The contributor licenses you to do everything with this software that would otherwise infringe their copyright in it. - -## Patent - -The contributor licenses you to do everything with this software that would otherwise infringe any patents they can license or become able to license. - -## Reliability - -The contributor can't revoke this license. - -## No Liability - -***As far as the law allows, this software comes as is, without any warranty or condition, and the contributor won't be liable to anyone for any damages related to this software or this license, under any kind of legal claim.*** diff --git a/README.md b/README.md index 7adf80a..9e004d7 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,15 @@ xml-like semantics that looks like you're invoking a bunch of CLI commands! It's meant to be used both as a serialization format and a configuration language, and is relatively light on syntax compared to XML. -## Intro +This repository is the place for discussing the [specification](SPEC.md). + +## Design and Discussion + +kdl is still extremely new, and discussion about the format should happen over +on the [discussions page](https://github.com/kdoclang/kdl/discussions). Feel free +to jump in and give us your 2 cents! + +## Overview The basic syntax is similar to SDLang: @@ -97,61 +105,10 @@ The following SDLang features are removed altogether: * Shell style (`#`) and Lua-style (`--`) comments * Distinction between 32/64/128-bit numbers. There's just numbers. -## Design and Discussion - -kdl is still extremely new, and discussion about the format should happen over -on the [discussions page](https://github.com/zkat/kdl/discussions). Feel free -to jump in and give us your 2 cents! - -## Grammar - -``` -nodes := linespace* (node (newline nodes)? linespace*)? - -node := identifier (node-space node-argument)* (node-space node-document)? single-line-comment? -node-argument := prop | value -node-children := '{' nodes '}' -node-space := ws* escline ws* | ws+ - -identifier := [a-zA-Z] [a-zA-Z0-9!$%&'*+\-./:<>?@\^_|~]* | string -prop := identifier '=' value -value := string | raw_string | number | boolean | 'null' - -string := '"' character* '"' -character := '\' escape | [^\"] -escape := ["\\/bfnrt] | 'u{' hex-digit{1, 6} '}' -hex-digit := [0-9a-fA-F] - -raw-string := 'r' raw-string-hash -raw-string-hash := '#' raw-string-hash '#' | raw-string-quotes -raw-string-quotes := '"' .* '"' - -number := decimal | hex | octal | binary - -decimal := integer ('.' [0-9]+)? exponent? -exponent := ('e' | 'E') integer -integer := sign? [0-9] [0-9_]* -sign := '+' | '-' - -hex := '0x' hex-digit (hex-digit | '_')* -octal := '0o' [0-7] [0-7_]* -binary := '0b' ('0' | '1') ('0' | '1' | '_')* - -boolean := 'true' | 'false' - -escline := '\\' ws* (single-line-comment | newline) - -linespace := newline | ws | single-line-comment - -newline := ('\r' '\n') | '\n' - -ws := bom | ' ' | '\t' | multi-line-comment - -single-line-comment := '//' ('\r' [^\n] | [^\r\n])* newline -multi-line-comment := '/*' ('*' [^\/] | [^*])* '*/' -``` - ## LICENSE -The above grammar/spec is licensed CC-BY-SA. The included [LICENSE.md -file](LICENSE.md) in this repository only covers this implementation. +This specification is covered under the [CC-BY-SA]() license. You are free to +write tools that handle KDL without worrying about licensing weirdness. If you +fork KDL into your own language and base it on this specification, though, +please make sure to publish that fork and clearly attribute/link to this +project. diff --git a/SPEC.md b/SPEC.md new file mode 100644 index 0000000..5b0d86f --- /dev/null +++ b/SPEC.md @@ -0,0 +1,52 @@ +# KDL Spec + +This is the kinda-formal specification for KDL, including the intended data +model and the grammar. + +## Full Grammar + +``` +nodes := linespace* (node (newline nodes)? linespace*)? + +node := identifier (node-space node-argument)* (node-space node-document)? single-line-comment? +node-argument := prop | value +node-children := '{' nodes '}' +node-space := ws* escline ws* | ws+ + +identifier := [a-zA-Z] [a-zA-Z0-9!$%&'*+\-./:<>?@\^_|~]* | string +prop := identifier '=' value +value := string | raw_string | number | boolean | 'null' + +string := '"' character* '"' +character := '\' escape | [^\"] +escape := ["\\/bfnrt] | 'u{' hex-digit{1, 6} '}' +hex-digit := [0-9a-fA-F] + +raw-string := 'r' raw-string-hash +raw-string-hash := '#' raw-string-hash '#' | raw-string-quotes +raw-string-quotes := '"' .* '"' + +number := decimal | hex | octal | binary + +decimal := integer ('.' [0-9]+)? exponent? +exponent := ('e' | 'E') integer +integer := sign? [0-9] [0-9_]* +sign := '+' | '-' + +hex := '0x' hex-digit (hex-digit | '_')* +octal := '0o' [0-7] [0-7_]* +binary := '0b' ('0' | '1') ('0' | '1' | '_')* + +boolean := 'true' | 'false' + +escline := '\\' ws* (single-line-comment | newline) + +linespace := newline | ws | single-line-comment + +newline := ('\r' '\n') | '\n' + +ws := bom | ' ' | '\t' | multi-line-comment + +single-line-comment := '//' ('\r' [^\n] | [^\r\n])* newline +multi-line-comment := '/*' ('*' [^\/] | [^*])* '*/' +``` diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index 7fd6345..0000000 --- a/src/error.rs +++ /dev/null @@ -1,74 +0,0 @@ -use std::num::{ParseFloatError, ParseIntError}; - -use nom::error::{ContextError, ErrorKind, FromExternalError, ParseError}; - -use thiserror::Error; -#[derive(Debug, Clone, Eq, PartialEq, Error)] -#[error("Error parsing document. {kind}")] -pub struct KdlError { - pub input: String, - pub offset: usize, - pub kind: KdlErrorKind, -} - -#[derive(Debug, Clone, Eq, PartialEq, Error)] -pub enum KdlErrorKind { - #[error(transparent)] - ParseIntError(ParseIntError), - #[error(transparent)] - ParseFloatError(ParseFloatError), - #[error("Failed to parse {0} component of semver string.")] - Context(&'static str), - #[error("Incomplete input to semver parser.")] - IncompleteInput, - #[error("An unspecified error occurred.")] - Other, -} - -#[derive(Debug, Clone, Eq, PartialEq)] -pub(crate) struct KdlParseError { - pub(crate) input: I, - pub(crate) context: Option<&'static str>, - pub(crate) kind: Option, -} - -impl ParseError for KdlParseError { - fn from_error_kind(input: I, _kind: nom::error::ErrorKind) -> Self { - Self { - input, - context: None, - kind: None, - } - } - - fn append(_input: I, _kind: nom::error::ErrorKind, other: Self) -> Self { - other - } -} - -impl ContextError for KdlParseError { - fn add_context(_input: I, ctx: &'static str, mut other: Self) -> Self { - other.context = Some(ctx); - other - } -} - -impl<'a> FromExternalError<&'a str, ParseIntError> for KdlParseError<&'a str> { - fn from_external_error(input: &'a str, _kind: ErrorKind, e: ParseIntError) -> Self { - KdlParseError { - input, - context: None, - kind: Some(KdlErrorKind::ParseIntError(e)), - } - } -} - -impl<'a> FromExternalError<&'a str, ParseFloatError> for KdlParseError<&'a str> { - fn from_external_error(input: &'a str, _kind: ErrorKind, e: ParseFloatError) -> Self { - KdlParseError { - input, - context: None, - kind: Some(KdlErrorKind::ParseFloatError(e)), - } - } -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index f1abdec..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,37 +0,0 @@ -use nom::combinator::all_consuming; -use nom::Err; - -pub use crate::error::{KdlError, KdlErrorKind}; -pub use crate::node::KdlNode; - -mod error; -mod node; -mod parser; - -pub fn parse_document(input: I) -> Result, KdlError> -where - I: AsRef, -{ - let input = &input.as_ref()[..]; - match all_consuming(parser::nodes)(input) { - Ok((_, arg)) => Ok(arg), - Err(err) => Err(match err { - Err::Error(e) | Err::Failure(e) => KdlError { - input: input.into(), - offset: e.input.as_ptr() as usize - input.as_ptr() as usize, - kind: if let Some(kind) = e.kind { - kind - } else if let Some(ctx) = e.context { - KdlErrorKind::Context(ctx) - } else { - KdlErrorKind::Other - }, - }, - Err::Incomplete(_) => KdlError { - input: input.into(), - offset: input.len() - 1, - kind: KdlErrorKind::IncompleteInput, - }, - }), - } -} diff --git a/src/node.rs b/src/node.rs deleted file mode 100644 index 72860e7..0000000 --- a/src/node.rs +++ /dev/null @@ -1,18 +0,0 @@ -use std::collections::HashMap; - -#[derive(Debug, Clone, PartialEq)] -pub struct KdlNode { - pub name: String, - pub values: Vec, - pub properties: HashMap, - pub children: Vec, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum KdlNodeValue { - Int(i64), - Float(f64), - String(String), - Boolean(bool), - Null, -} diff --git a/src/parser.rs b/src/parser.rs deleted file mode 100644 index 289611f..0000000 --- a/src/parser.rs +++ /dev/null @@ -1,473 +0,0 @@ -use std::collections::HashMap; - -use nom::branch::alt; -use nom::bytes::complete::{is_not, tag, take_until, take_while_m_n}; -use nom::character::complete::{alpha1, alphanumeric1, char, none_of, one_of}; -use nom::combinator::{eof, map, map_opt, map_res, opt, recognize, value}; -use nom::multi::{fold_many0, many0, many1}; -use nom::sequence::{delimited, pair, preceded, terminated, tuple}; -use nom::IResult; - -use crate::error::KdlParseError; -use crate::node::{KdlNode, KdlNodeValue}; - -/// `nodes := linespace* (node (newline document)?)?` -pub(crate) fn nodes(input: &str) -> IResult<&str, Vec, KdlParseError<&str>> { - many0(delimited(many0(linespace), node, newline))(input) -} - -#[derive(Clone)] -enum NodeArg { - Value(KdlNodeValue), - Property(String, KdlNodeValue), -} - -/// `node := identifier (node-space node-argument)* (node-space node-document)?` -pub(crate) fn node(input: &str) -> IResult<&str, KdlNode, KdlParseError<&str>> { - let (input, tag) = identifier(input)?; - let (input, args) = many0(preceded(node_space, node_arg))(input)?; - let (input, children) = opt(preceded(node_space, node_children))(input)?; - let (values, properties): (Vec, Vec) = args - .into_iter() - .partition(|arg| matches!(arg, NodeArg::Value(_))); - Ok(( - input, - KdlNode { - name: tag, - children: children.unwrap_or_else(Vec::new), - values: values - .into_iter() - .map(|arg| match arg { - NodeArg::Value(val) => val, - _ => unreachable!(), - }) - .collect(), - properties: properties.into_iter().fold(HashMap::new(), |mut acc, arg| { - match arg { - NodeArg::Property(key, value) => { - acc.insert(key, value); - } - _ => unreachable!(), - } - acc - }), - }, - )) -} - -/// `identifier := [a-zA-Z_] [a-zA-Z0-9!$%&'*+\-./:<>?@\^_|~]* | string` -fn identifier(input: &str) -> IResult<&str, String, KdlParseError<&str>> { - alt(( - map( - recognize(pair( - alt((alpha1, tag("_"))), - many0(alt((alphanumeric1, recognize(one_of("~!@$%^&*-_+./:<>?"))))), - )), - String::from, - ), - string, - ))(input) -} - -fn node_arg(input: &str) -> IResult<&str, NodeArg, KdlParseError<&str>> { - alt(( - map(property, |(key, val)| NodeArg::Property(key, val)), - map(node_value, NodeArg::Value), - ))(input) -} - -/// `prop := identifier '=' value` -fn property(input: &str) -> IResult<&str, (String, KdlNodeValue), KdlParseError<&str>> { - let (input, key) = identifier(input)?; - let (input, _) = tag("=")(input)?; - let (input, val) = node_value(input)?; - Ok((input, (key, val))) -} - -/// `value := string | raw_string | number | boolean | 'null'` -fn node_value(input: &str) -> IResult<&str, KdlNodeValue, KdlParseError<&str>> { - alt(( - map(string, KdlNodeValue::String), - map(raw_string, |s| KdlNodeValue::String(s.into())), - number, - boolean, - value(KdlNodeValue::Null, tag("null")), - ))(input) -} - -/// `node-children := '{' nodes '}'` -fn node_children(input: &str) -> IResult<&str, Vec, KdlParseError<&str>> { - delimited(tag("{"), nodes, tag("}"))(input) -} - -/// `string := '"' character* '"'` -fn string(input: &str) -> IResult<&str, String, KdlParseError<&str>> { - delimited( - char('"'), - fold_many0(character, String::new(), |mut acc, ch| { - acc.push(ch); - acc - }), - char('"'), - )(input) -} - -/// `character := '\' escape | [^\"]` -fn character(input: &str) -> IResult<&str, char, KdlParseError<&str>> { - alt((preceded(char('\\'), escape), none_of("\\\"")))(input) -} - -/// `escape := ["\\/bfnrt] | 'u{' hex-digit{1, 6} '}'` -fn escape(input: &str) -> IResult<&str, char, KdlParseError<&str>> { - alt(( - delimited(tag("u{"), unicode, char('}')), - value('"', char('"')), - value('\\', char('\\')), - value('/', char('/')), - value('\u{08}', char('b')), - value('\u{0C}', char('f')), - value('\n', char('n')), - value('\r', char('r')), - value('\t', char('t')), - ))(input) -} - -fn unicode(input: &str) -> IResult<&str, char, KdlParseError<&str>> { - map_opt( - map_res( - take_while_m_n(1, 6, |c: char| c.is_ascii_hexdigit()), - |hex| u32::from_str_radix(hex, 16), - ), - std::char::from_u32, - )(input) -} - -/// `raw-string := 'r' raw-string-hash` -/// `raw-string-hash := '#' raw-string-hash '#' | raw-string-quotes` -/// `raw-string-quotes := '"' .* '"'` -fn raw_string(input: &str) -> IResult<&str, &str, KdlParseError<&str>> { - let (input, _) = char('r')(input)?; - let (input, hashes) = recognize(many0(char('#')))(input)?; - let (input, _) = char('"')(input)?; - let close = format!("\"{}", hashes); - let (input, string) = take_until(&close[..])(input)?; - let (input, _) = tag(&close[..])(input)?; - Ok((input, string)) -} - -/// `number := decimal | hex | octal | binary` -fn number(input: &str) -> IResult<&str, KdlNodeValue, KdlParseError<&str>> { - alt(( - map(integer, KdlNodeValue::Int), - map(hexadecimal, KdlNodeValue::Int), - map(octal, KdlNodeValue::Int), - map(binary, KdlNodeValue::Int), - map(float, KdlNodeValue::Float), - ))(input) -} - -/// ```text -/// decimal := integer ('.' [0-9]+)? exponent? -/// exponent := ('e' | 'E') integer -/// integer := sign? [1-9] [0-9_]* -/// sign := '+' | '-' -/// ``` -fn float(input: &str) -> IResult<&str, f64, KdlParseError<&str>> { - map_res( - alt(( - recognize(tuple(( - integer, - opt(preceded(char('.'), integer)), - one_of("eE"), - opt(one_of("+-")), - integer, - ))), - recognize(tuple((integer, char('.'), integer))), - )), - |x| str::replace(x, "_", "").parse::(), - )(input) -} - -/// ```text -/// decimal := integer ('.' [0-9]+)? exponent? -/// exponent := ('e' | 'E') integer -/// integer := sign? [1-9] [0-9_]* -/// sign := '+' | '-' -/// ``` -fn integer(input: &str) -> IResult<&str, i64, KdlParseError<&str>> { - let (input, sign) = opt(alt((char('+'), char('-'))))(input)?; - let mult = if let Some(sign) = sign { - if sign == '+' { - 1 - } else { - -1 - } - } else { - 1 - }; - map_res( - recognize(many1(terminated(one_of("0123456789"), many0(char('_'))))), - move |out: &str| { - i64::from_str_radix(&str::replace(&out, "_", ""), 10).map(move |x| x * mult) - }, - )(input) -} - -/// `hex := '0x' [0-9a-fA-F] [0-9a-fA-F_]*` -fn hexadecimal(input: &str) -> IResult<&str, i64, KdlParseError<&str>> { - map_res( - preceded( - alt((tag("0x"), tag("0X"))), - recognize(many1(terminated( - one_of("0123456789abcdefABCDEF"), - many0(char('_')), - ))), - ), - move |out: &str| i64::from_str_radix(&str::replace(&out, "_", ""), 16), - )(input) -} - -/// `octal := '0o' [0-7] [0-7_]*` -fn octal(input: &str) -> IResult<&str, i64, KdlParseError<&str>> { - map_res( - preceded( - alt((tag("0o"), tag("0O"))), - recognize(many1(terminated(one_of("01234567"), many0(char('_'))))), - ), - move |out: &str| i64::from_str_radix(&str::replace(&out, "_", ""), 8), - )(input) -} - -/// `binary := '0b' ('0' | '1') ('0' | '1' | '_')*` -fn binary(input: &str) -> IResult<&str, i64, KdlParseError<&str>> { - map_res( - preceded( - alt((tag("0b"), tag("0B"))), - recognize(many1(terminated(one_of("01"), many0(char('_'))))), - ), - move |out: &str| i64::from_str_radix(&str::replace(&out, "_", ""), 2), - )(input) -} - -/// `boolean := 'true' | 'false'` -fn boolean(input: &str) -> IResult<&str, KdlNodeValue, KdlParseError<&str>> { - alt(( - value(KdlNodeValue::Boolean(true), tag("true")), - value(KdlNodeValue::Boolean(false), tag("false")), - ))(input) -} - -/// `node-space := ws* escline ws* | ws+` -fn node_space(input: &str) -> IResult<&str, (), KdlParseError<&str>> { - alt(( - delimited(many0(whitespace), escline, many0(whitespace)), - map(many1(whitespace), |_| ()), - ))(input) -} - -/// `single-line-comment := '//' ('\r' [^\n] | [^\r\n])* (newline | eof)` -fn single_line_comment(input: &str) -> IResult<&str, (), KdlParseError<&str>> { - let (input, _) = tag("//")(input)?; - let (input, _) = alt((take_until("\r\n"), is_not("\n")))(input)?; - let (input, _) = alt((newline, value((), eof)))(input)?; - Ok((input, ())) -} - -/// `multi-line-comment := '/*' ('*' [^\/] | [^*])* '*/'` -fn multi_line_comment(input: &str) -> IResult<&str, (), KdlParseError<&str>> { - delimited(tag("/*"), value((), take_until("*/")), tag("*/"))(input) -} - -/// `escline := '\\' ws* (single-line-comment | newline)` -fn escline(input: &str) -> IResult<&str, (), KdlParseError<&str>> { - let (input, _) = tag("\\")(input)?; - let (input, _) = many0(whitespace)(input)?; - let (input, _) = alt((single_line_comment, newline))(input)?; - Ok((input, ())) -} - -/// `linespace := newline | ws | single-line-comment` -fn linespace(input: &str) -> IResult<&str, (), KdlParseError<&str>> { - value((), alt((newline, whitespace, single_line_comment)))(input) -} - -/// `ws := bom | ' ' | '\t' | multi-line-comment` -fn whitespace(input: &str) -> IResult<&str, (), KdlParseError<&str>> { - // TODO: bom? - value( - (), - alt(( - /*bom,*/ tag(" "), - tag("\t"), - recognize(multi_line_comment), - )), - )(input) -} - -/// `newline := ('\r' '\n') | '\n'` -fn newline(input: &str) -> IResult<&str, (), KdlParseError<&str>> { - value((), alt((tag("\r\n"), tag("\n"))))(input) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_string() { - assert_eq!(string("\"\""), Ok(("", "".into()))); - assert_eq!(string("\"hello\""), Ok(("", "hello".into()))); - assert_eq!(string("\"hello\nworld\""), Ok(("", "hello\nworld".into()))); - assert_eq!(string("\"\u{10FFF}\""), Ok(("", "\u{10FFF}".into()))); - assert_eq!( - string(r#""\"\\\/\b\f\n\r\t""#), - Ok(("", "\"\\/\u{08}\u{0C}\n\r\t".into())) - ); - assert_eq!(string(r#""\u{10}""#), Ok(("", "\u{10}".into()))); - assert!(string(r#""\i""#).is_err()); - assert!(string(r#""\u{c0ffee}""#).is_err()); - } - - #[test] - fn test_float() { - assert_eq!(float("1.0"), Ok(("", 1.0f64))); - assert_eq!(float("0.0"), Ok(("", 0.0f64))); - assert_eq!(float("-1.0"), Ok(("", -1.0f64))); - assert_eq!(float("+1.0"), Ok(("", 1.0f64))); - assert_eq!(float("1.0e10"), Ok(("", 1.0e10f64))); - assert_eq!(float("1.0e-10"), Ok(("", 1.0e-10f64))); - assert_eq!(float("-1.0e-10"), Ok(("", -1.0e-10f64))); - assert_eq!(float("123_456_789.0"), Ok(("", 123456789.0f64))); - assert_eq!(float("123_456_789.0_"), Ok(("", 123456789.0f64))); - assert!(float("?1.0").is_err()); - assert!(float("_1.0").is_err()); - assert!(float("1._0").is_err()); - assert!(float("1.").is_err()); - assert!(float(".0").is_err()); - } - - #[test] - fn test_integer() { - assert_eq!(integer("0"), Ok(("", 0))); - assert_eq!(integer("0123456789"), Ok(("", 123456789))); - assert_eq!(integer("0123_456_789"), Ok(("", 123456789))); - assert_eq!(integer("0123_456_789_"), Ok(("", 123456789))); - assert_eq!(integer("+0123456789"), Ok(("", 123456789))); - assert_eq!(integer("-0123456789"), Ok(("", -123456789))); - assert!(integer("?0123456789").is_err()); - assert!(integer("_0123456789").is_err()); - assert!(integer("a").is_err()); - assert!(integer("--").is_err()); - } - - #[test] - fn test_hexadecimal() { - assert_eq!( - hexadecimal("0x0123456789abcdef"), - Ok(("", 0x0123456789abcdef)) - ); - assert_eq!( - hexadecimal("0x01234567_89abcdef"), - Ok(("", 0x0123456789abcdef)) - ); - assert_eq!( - hexadecimal("0x01234567_89abcdef_"), - Ok(("", 0x0123456789abcdef)) - ); - assert!(hexadecimal("0x_123").is_err()); - assert!(hexadecimal("0xg").is_err()); - assert!(hexadecimal("0xx").is_err()); - } - - #[test] - fn test_octal() { - assert_eq!(octal("0o01234567"), Ok(("", 0o01234567))); - assert_eq!(octal("0o0123_4567"), Ok(("", 0o01234567))); - assert_eq!(octal("0o01234567_"), Ok(("", 0o01234567))); - assert!(octal("0o_123").is_err()); - assert!(octal("0o8").is_err()); - assert!(octal("0oo").is_err()); - } - - #[test] - fn test_binary() { - assert_eq!(binary("0b0101"), Ok(("", 0b0101))); - assert_eq!(binary("0b01_10"), Ok(("", 0b0110))); - assert_eq!(binary("0b01___10"), Ok(("", 0b0110))); - assert_eq!(binary("0b0110_"), Ok(("", 0b0110))); - assert!(binary("0b_0110").is_err()); - assert!(binary("0b20").is_err()); - assert!(binary("0bb").is_err()); - } - - #[test] - fn test_raw_string() { - assert_eq!(raw_string(r#"r"foo""#), Ok(("", "foo"))); - assert_eq!(raw_string("r\"foo\nbar\""), Ok(("", "foo\nbar"))); - assert_eq!(raw_string(r##"r#"foo"#"##), Ok(("", "foo"))); - assert_eq!(raw_string(r###"r##"foo"##"###), Ok(("", "foo"))); - assert_eq!(raw_string(r#"r"\nfoo\r""#), Ok(("", r"\nfoo\r"))); - assert!(raw_string(r###"r##"foo"#"###).is_err()); - } - - #[test] - fn test_boolean() { - assert_eq!(boolean("true"), Ok(("", KdlNodeValue::Boolean(true)))); - assert_eq!(boolean("false"), Ok(("", KdlNodeValue::Boolean(false)))); - assert!(boolean("blah").is_err()); - } - - #[test] - fn test_node_space() { - assert_eq!(node_space(" "), Ok(("", ()))); - assert_eq!(node_space("\t "), Ok(("", ()))); - assert_eq!(node_space("\t \\ // hello\n "), Ok(("", ()))); - assert!(node_space("blah").is_err()); - } - - #[test] - fn test_single_line_comment() { - assert_eq!(single_line_comment("//hello"), Ok(("", ()))); - assert_eq!(single_line_comment("// \thello"), Ok(("", ()))); - assert_eq!(single_line_comment("//hello\n"), Ok(("", ()))); - assert_eq!(single_line_comment("//hello\r\n"), Ok(("", ()))); - assert_eq!(single_line_comment("//hello\n\r"), Ok(("\r", ()))); - assert_eq!(single_line_comment("//hello\rworld"), Ok(("", ()))); - } - - #[test] - fn test_multi_line_comment() { - assert_eq!(multi_line_comment("/*hello*/"), Ok(("", ()))); - assert_eq!(multi_line_comment("/*hello*/\n"), Ok(("\n", ()))); - assert_eq!(multi_line_comment("/*\nhello\r\n*/"), Ok(("", ()))); - assert_eq!(multi_line_comment("/*\nhello** /\n*/"), Ok(("", ()))); - assert_eq!(multi_line_comment("/**\nhello** /\n*/"), Ok(("", ()))); - assert_eq!(multi_line_comment("/*hello*/world"), Ok(("world", ()))); - } - - #[test] - fn test_escline() { - assert_eq!(escline("\\\nfoo"), Ok(("foo", ()))); - assert_eq!(escline("\\\n foo"), Ok((" foo", ()))); - assert_eq!(escline("\\ \t \nfoo"), Ok(("foo", ()))); - assert_eq!(escline("\\ // test \nfoo"), Ok(("foo", ()))); - assert_eq!(escline("\\ // test \n foo"), Ok((" foo", ()))); - } - - #[test] - fn test_whitespace() { - assert_eq!(whitespace(" "), Ok(("", ()))); - assert_eq!(whitespace("\t"), Ok(("", ()))); - assert_eq!(whitespace("/* \nfoo\r\n */ etc"), Ok((" etc", ()))); - assert!(whitespace("hi").is_err()) - } - - #[test] - fn test_newline() { - assert_eq!(newline("\n"), Ok(("", ()))); - assert_eq!(newline("\r\n"), Ok(("", ()))); - assert_eq!(newline("\n\n"), Ok(("\n", ()))); - assert!(newline("\r").is_err()); - assert!(newline("blah").is_err()); - } -}