diff --git a/Cargo.toml b/Cargo.toml index 1d7a1a6..ab6b211 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,14 @@ [package] name = "rsn" +description = "A Rust-inspired, human-readable object notation." version = "0.1.0" edition = "2021" license = "MIT OR Apache-2.0" repository = "https://github.com/khonsulabs/rsn" readme = "./README.md" rust-version = "1.65" +categories = ["no-std", "parser-implementations", "encoding"] +keywords = ["serde", "parser", "serialization"] [features] default = ["serde", "std"] diff --git a/README.md b/README.md index e024388..ccc8010 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,13 @@ # Rsn - Rusty Notation -**This crate is very early in development and is not ready for consumption.** +**This crate is very early in development. Please report any issues [on our +GitHub](https://github.com/khonsulabs/rsn).** ![rsn forbids unsafe code](https://img.shields.io/badge/unsafe-forbid-success) ![rsn is considered alpha](https://img.shields.io/badge/status-alpha-orange) [![crate version](https://img.shields.io/crates/v/rsn.svg)](https://crates.io/crates/rsn) [![Live Build Status](https://img.shields.io/github/actions/workflow/status/khonsulabs/rsn/rust.yml?branch=main)](https://github.com/khonsulabs/rsn/actions?query=workflow:Tests) -[![HTML Coverage Report for `main`](https://khonsulabs.github.io/rsn/coverage/badge.svg)]($pages-base$/coverage/) +[![HTML Coverage Report for `main`](https://khonsulabs.github.io/rsn/coverage/badge.svg)](https://khonsulabs.github.io/rsn/coverage/) [![Documentation for `main`](https://img.shields.io/badge/docs-main-informational)](https://khonsulabs.github.io/rsn/main/rsn/) A UTF-8 based text format that looks very similar to valid Rust code. This format adheres closely to [Rust's lexical rules][rust-lexer] @@ -17,15 +18,40 @@ This crate supports `no_std` targets that support the `alloc` crate. ## Data Types +```rsn +ExampleStruct { + integers: [42, 0xFF, 0o77, 0b101], + floats: [42., 3.14, 1e10], + bools: [true, false], + chars: ['a', '\''], + string: "Hello, World!", + raw_string: r#"I said, "Hello, World!""#, + bytes: [b'a', b'\''], + byte_string: b"Hello, World!", + raw_byte_string: br#"I said, "Hello, World!""#, + named_map: StructLike { + field: 42, + }, + named_tuple: TupleLike(42), + r#raw_identifiers: true, + array: [1, 2, 3], + tuple: (1, 2, 3), + map: { + "a": 1, + "b": 2, + }, +} +``` + - Integers (`42`, `0xFF`, `0o77`, `0b101`) -- Floats (`42.`, `3.14`, `) +- Floats (`42.`, `3.14`) - Bool (`true`, `false`) - Character (`'a'`, `'\''`) - Byte (`b'a'`, `b'\''`) - String (`"hello, world"`) - Raw Strings (`r#"They said, "Hello World!""#`) - Byte Strings (`b"hello, world"`) -- Struct +- Named - Ident or Raw Ident (`r#foo`) - Map or Tuple - Map diff --git a/examples/alltypes.rs b/examples/alltypes.rs new file mode 100644 index 0000000..c5031ed --- /dev/null +++ b/examples/alltypes.rs @@ -0,0 +1,41 @@ +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; +use serde_bytes::ByteBuf; + +#[derive(Serialize, Deserialize, Debug)] +struct ExampleStruct { + integers: Vec, + floats: Vec, + bools: Vec, + chars: Vec, + string: String, + raw_string: String, + bytes: Vec, + byte_string: ByteBuf, + raw_byte_string: ByteBuf, + named_map: NamedExample, + named_tuple: NamedExample, + r#raw_identifiers: bool, + array: Vec, + tuple: Vec, + map: HashMap, +} + +#[derive(Serialize, Deserialize, Debug)] +enum NamedExample { + StructLike { field: usize }, + TupleLike(usize), +} + +fn main() { + let example: ExampleStruct = + rsn::from_str(include_str!("./alltypes.rsn")).expect("error deserializing alltypes.rsn"); + + println!("Loaded blog posts: {example:?}"); +} + +#[test] +fn runs() { + main(); +} diff --git a/examples/alltypes.rsn b/examples/alltypes.rsn new file mode 100644 index 0000000..e71e2bf --- /dev/null +++ b/examples/alltypes.rsn @@ -0,0 +1,22 @@ +ExampleStruct { + integers: [42, 0xFF, 0o77, 0b101], + floats: [42., 3.14, 1e10], + bools: [true, false], + chars: ['a', '\''], + string: "Hello, World!", + raw_string: r#"I said, "Hello, World!""#, + bytes: [b'a', b'\''], + byte_string: b"Hello, World!", + raw_byte_string: br#"I said, "Hello, World!""#, + named_map: StructLike { + field: 42, + }, + named_tuple: TupleLike(42), + r#raw_identifiers: true, + array: [1, 2, 3], + tuple: (1, 2, 3), + map: { + "a": 1, + "b": 2, + }, +} \ No newline at end of file diff --git a/src/de.rs b/src/de.rs index 64eaea4..3a31805 100644 --- a/src/de.rs +++ b/src/de.rs @@ -424,11 +424,11 @@ impl<'de> serde::de::Deserializer<'de> for &mut Deserializer<'de> { Some(Ok(Event { kind: EventKind::BeginNested { - name, + name: Some(Name { name: "Some", .. }), kind: Nested::Tuple, }, .. - })) if matches!(name, Some(Name { name: "Some", .. })) => { + })) => { de.parser.next(); let result = visitor.visit_some(&mut *de)?; match de.parser.next().transpose()? { diff --git a/src/parser.rs b/src/parser.rs index 5ae86bd..46857c5 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -116,7 +116,13 @@ impl<'s> Parser<'s> { .. }) ) { - let Some(Ok(Token { kind: TokenKind::Open(balanced), location: open_location })) = self.next_token() else { unreachable!("matched above") }; + let Some(Ok(Token { + kind: TokenKind::Open(balanced), + location: open_location, + })) = self.next_token() + else { + unreachable!("matched above") + }; let kind = match balanced { Balanced::Paren => { @@ -251,7 +257,9 @@ impl<'s> Parser<'s> { } fn map_state_mut(&mut self) -> &mut MapState { - let Some((_,NestedState::Map(map_state))) = self.nested.last_mut() else { unreachable!("not a map state") }; + let Some((_, NestedState::Map(map_state))) = self.nested.last_mut() else { + unreachable!("not a map state") + }; map_state } @@ -296,7 +304,7 @@ impl<'s> Parser<'s> { )), }, MapState::ExpectingComma => match self.next_token_parts()? { - (location, Some(TokenKind::Close(closed))) if closed == Balanced::Brace => { + (location, Some(TokenKind::Close(Balanced::Brace))) => { self.nested.pop(); Ok(Event::new(location, EventKind::EndNested)) } @@ -359,7 +367,7 @@ impl<'s> Parser<'s> { )), }, MapState::ExpectingComma => match self.next_token_parts()? { - (location, Some(TokenKind::Close(closed))) if closed == Balanced::Brace => { + (location, Some(TokenKind::Close(Balanced::Brace))) => { self.root_state = State::Finished; Ok(Event::new(location, EventKind::EndNested)) } @@ -396,8 +404,9 @@ impl<'s> Parser<'s> { }; match &token.kind { TokenKind::Identifier(_) if self.config.allow_implicit_map => { - let TokenKind::Identifier(identifier) = token.kind - else { unreachable!("just matched")}; + let TokenKind::Identifier(identifier) = token.kind else { + unreachable!("just matched") + }; match self.peek() { Some(colon) if matches!(colon.kind, TokenKind::Colon) => { // Switch to parsing an implicit map @@ -417,8 +426,13 @@ impl<'s> Parser<'s> { TokenKind::Open(Balanced::Brace | Balanced::Paren,) ) => { - let Some(Ok(Token{ kind: TokenKind::Open(kind), location: open_location})) = self.next_token() - else { unreachable!("just peeked") }; + let Some(Ok(Token { + kind: TokenKind::Open(kind), + location: open_location, + })) = self.next_token() + else { + unreachable!("just peeked") + }; self.root_state = State::Finished; Ok(Event::new( token.location, @@ -456,8 +470,12 @@ impl<'s> Parser<'s> { } } State::StartingImplicitMap(_) => { - let State::StartingImplicitMap((location, identifier)) = mem::replace(&mut self.root_state, State::ImplicitMap(MapState::ExpectingColon)) - else { unreachable!("just matched") }; + let State::StartingImplicitMap((location, identifier)) = mem::replace( + &mut self.root_state, + State::ImplicitMap(MapState::ExpectingColon), + ) else { + unreachable!("just matched") + }; Ok(Event::new( location, EventKind::Primitive(Primitive::Identifier(identifier)), diff --git a/src/tokenizer.rs b/src/tokenizer.rs index e1ebf33..5df6e6d 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -449,11 +449,8 @@ impl<'a, const INCLUDE_ALL: bool> Tokenizer<'a, INCLUDE_ALL> { self.scratch.clear(); let already_read_chars = self.chars.marked_str(); if had_underscores { - self.scratch.extend( - already_read_chars - .chars() - .filter_map(|ch| (ch != '_').then_some(ch)), - ); + self.scratch + .extend(already_read_chars.chars().filter(|ch| ch != &'_')); } else { self.scratch.push_str(already_read_chars); }