├── tests ├── test_no_std │ ├── .gitignore │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── compile_fail │ ├── empty.rs │ ├── interior-nul.rs │ ├── trash-after.rs │ ├── non-str.rs │ ├── trash-after.stderr │ ├── interior-nul.stderr │ ├── empty.stderr │ └── non-str.stderr ├── compile_test.rs ├── pass │ ├── byte_str_lit.rs │ ├── str_lit.rs │ ├── const.rs │ ├── ident.rs │ └── macro.rs └── clippy_lints.rs ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── .github └── workflows │ └── ci.yaml └── src ├── lib.rs └── parse.rs /tests/test_no_std/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /target/ 3 | /macros/target/ 4 | **/*.rs.bk 5 | Cargo.lock 6 | -------------------------------------------------------------------------------- /tests/compile_fail/empty.rs: -------------------------------------------------------------------------------- 1 | use cstr::cstr; 2 | 3 | fn main() { 4 | let _foo = cstr!(); 5 | } 6 | -------------------------------------------------------------------------------- /tests/compile_fail/interior-nul.rs: -------------------------------------------------------------------------------- 1 | use cstr::cstr; 2 | 3 | fn main() { 4 | let _foo = cstr!("foo\0bar"); 5 | } 6 | -------------------------------------------------------------------------------- /tests/compile_fail/trash-after.rs: -------------------------------------------------------------------------------- 1 | use cstr::cstr; 2 | 3 | fn main() { 4 | let _foo = cstr!("foo" + "bar"); 5 | } 6 | -------------------------------------------------------------------------------- /tests/compile_fail/non-str.rs: -------------------------------------------------------------------------------- 1 | use cstr::cstr; 2 | 3 | fn main() { 4 | let _foo = cstr!(1); 5 | let _foo = cstr!(("a")); 6 | let _foo = cstr!(&1); 7 | } 8 | -------------------------------------------------------------------------------- /tests/compile_fail/trash-after.stderr: -------------------------------------------------------------------------------- 1 | error: unexpected token 2 | --> $DIR/trash-after.rs:4:28 3 | | 4 | 4 | let _foo = cstr!("foo" + "bar"); 5 | | ^ 6 | -------------------------------------------------------------------------------- /tests/compile_test.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn compile_test() { 3 | let t = trybuild::TestCases::new(); 4 | t.pass("tests/pass/*.rs"); 5 | t.compile_fail("tests/compile_fail/*.rs"); 6 | } 7 | -------------------------------------------------------------------------------- /tests/compile_fail/interior-nul.stderr: -------------------------------------------------------------------------------- 1 | error: nul byte found in the literal 2 | --> $DIR/interior-nul.rs:4:22 3 | | 4 | 4 | let _foo = cstr!("foo\0bar"); 5 | | ^^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /tests/test_no_std/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test_no_std" 3 | version = "0.0.0" 4 | edition = "2021" 5 | license = "MIT" 6 | 7 | publish = false 8 | 9 | [dependencies] 10 | cstr.path = "../../" 11 | -------------------------------------------------------------------------------- /tests/pass/byte_str_lit.rs: -------------------------------------------------------------------------------- 1 | use cstr::cstr; 2 | use std::ffi::CStr; 3 | 4 | fn main() { 5 | let foo: &'static CStr = cstr!(b"foo\xffbar"); 6 | assert_eq!(foo, CStr::from_bytes_with_nul(b"foo\xffbar\0").unwrap()); 7 | } 8 | -------------------------------------------------------------------------------- /tests/pass/str_lit.rs: -------------------------------------------------------------------------------- 1 | use cstr::cstr; 2 | use std::ffi::CStr; 3 | 4 | fn main() { 5 | let foo: &'static CStr = cstr!("foo\u{4e00}bar"); 6 | let expected = b"foo\xe4\xb8\x80bar\0"; 7 | assert_eq!(foo, CStr::from_bytes_with_nul(expected).unwrap()); 8 | } 9 | -------------------------------------------------------------------------------- /tests/pass/const.rs: -------------------------------------------------------------------------------- 1 | use cstr::cstr; 2 | use std::ffi::CStr; 3 | 4 | const FOO: &CStr = cstr!(b"foo\xffbar"); 5 | static BAR: &CStr = cstr!("bar"); 6 | 7 | fn main() { 8 | assert_eq!(FOO, CStr::from_bytes_with_nul(b"foo\xffbar\0").unwrap()); 9 | assert_eq!(BAR, CStr::from_bytes_with_nul(b"bar\0").unwrap()); 10 | } 11 | -------------------------------------------------------------------------------- /tests/pass/ident.rs: -------------------------------------------------------------------------------- 1 | use cstr::cstr; 2 | use std::ffi::CStr; 3 | 4 | fn main() { 5 | let foo: &'static CStr = cstr!(foobar); 6 | assert_eq!(foo, CStr::from_bytes_with_nul(b"foobar\0").unwrap()); 7 | let foo: &'static CStr = cstr!(r#foobar); 8 | assert_eq!(foo, CStr::from_bytes_with_nul(b"r#foobar\0").unwrap()); 9 | } 10 | -------------------------------------------------------------------------------- /tests/clippy_lints.rs: -------------------------------------------------------------------------------- 1 | use cstr::cstr; 2 | use std::ffi::CStr; 3 | 4 | #[test] 5 | #[deny(clippy::transmute_ptr_to_ref)] 6 | fn deny_transmute_ptr_to_ref() { 7 | let s: &'static CStr = cstr!("foo\u{4e00}bar"); 8 | let expected = b"foo\xe4\xb8\x80bar\0"; 9 | assert_eq!(s, CStr::from_bytes_with_nul(expected).unwrap()); 10 | } 11 | -------------------------------------------------------------------------------- /tests/compile_fail/empty.stderr: -------------------------------------------------------------------------------- 1 | error: unexpected end of input, expected one of: byte string literal, string literal, identifier 2 | --> $DIR/empty.rs:4:16 3 | | 4 | 4 | let _foo = cstr!(); 5 | | ^^^^^^^ 6 | | 7 | = note: this error originates in the macro `cstr` (in Nightly builds, run with -Z macro-backtrace for more info) 8 | -------------------------------------------------------------------------------- /tests/pass/macro.rs: -------------------------------------------------------------------------------- 1 | use cstr::cstr; 2 | use std::ffi::CStr; 3 | 4 | macro_rules! cstr_expr { 5 | ($s:expr) => { 6 | cstr!($s) 7 | }; 8 | } 9 | 10 | macro_rules! cstr_literal { 11 | ($s:literal) => { 12 | cstr!($s) 13 | }; 14 | } 15 | 16 | fn main() { 17 | let foo: &'static CStr = cstr_expr!("foo"); 18 | assert_eq!(foo, CStr::from_bytes_with_nul(b"foo\0").unwrap()); 19 | let bar: &'static CStr = cstr_literal!("bar"); 20 | assert_eq!(bar, CStr::from_bytes_with_nul(b"bar\0").unwrap()); 21 | } 22 | -------------------------------------------------------------------------------- /tests/compile_fail/non-str.stderr: -------------------------------------------------------------------------------- 1 | error: expected one of: byte string literal, string literal, identifier 2 | --> $DIR/non-str.rs:4:22 3 | | 4 | 4 | let _foo = cstr!(1); 5 | | ^ 6 | 7 | error: expected one of: byte string literal, string literal, identifier 8 | --> $DIR/non-str.rs:5:22 9 | | 10 | 5 | let _foo = cstr!(("a")); 11 | | ^^^^^ 12 | 13 | error: expected one of: byte string literal, string literal, identifier 14 | --> $DIR/non-str.rs:6:22 15 | | 16 | 6 | let _foo = cstr!(&1); 17 | | ^ 18 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cstr" 3 | version = "0.2.12" 4 | authors = ["Xidorn Quan "] 5 | description = "Macro for building static CStr reference" 6 | repository = "https://github.com/upsuper/cstr" 7 | license = "MIT" 8 | keywords = ["macro", "cstr"] 9 | readme = "README.md" 10 | edition = "2021" 11 | rust-version = "1.64" 12 | 13 | [lib] 14 | proc-macro = true 15 | 16 | [badges] 17 | travis-ci = { repository = "upsuper/cstr", branch = "master" } 18 | 19 | [dependencies] 20 | proc-macro2 = "1" 21 | quote = "1" 22 | 23 | [dev-dependencies] 24 | trybuild = "1.0.30" 25 | -------------------------------------------------------------------------------- /tests/test_no_std/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Verifies that cstr! can be used on no_std systems. 2 | //! 3 | //! To ensure that `std` is not sneaked in through a dependency (even though this crate has none at 4 | //! runtime), this should best be built on a target that has no `std` because it has no operating 5 | //! system, eg. thumbv7em-none-eabi. 6 | //! 7 | //! Note that building the [`cstr`] crate alone is insufficient, as it does not run throuogh any 8 | //! `cstr!()` code generation and thus not trip over std-isms in the generated code. 9 | #![no_std] 10 | 11 | use core::ffi::CStr; 12 | 13 | pub fn can_use_cstr_macro() -> &'static CStr { 14 | cstr::cstr!("Hello World!") 15 | } 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018-2020 Xidorn Quan 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cstr 2 | 3 | [![CI](https://github.com/upsuper/cstr/workflows/CI/badge.svg)](https://github.com/upsuper/cstr/actions) 4 | [![Crates.io](https://img.shields.io/crates/v/cstr.svg)](https://crates.io/crates/cstr) 5 | [![Docs](https://docs.rs/cstr/badge.svg)](https://docs.rs/cstr) 6 | 7 | 8 | 9 | **This crate has been deprecated. 10 | Rust 1.77.0 stabilized [C-string literals][c-string-literal]. 11 | From that version, `c"abc"` can be used in place of `cstr!("abc")` provided by this crate. 12 | This new feature gives more concise code and faster compilation. 13 | Hence, this crate will no longer be maintained.** 14 | 15 | [c-string-literal]: https://blog.rust-lang.org/2024/03/21/Rust-1.77.0.html#c-string-literals 16 | 17 | A macro for getting `&'static CStr` from literal or identifier. 18 | 19 | This macro checks whether the given literal is valid for `CStr` 20 | at compile time, and returns a static reference of `CStr`. 21 | 22 | This macro can be used to initialize constants. 23 | 24 | ## Example 25 | 26 | ```rust 27 | use cstr::cstr; 28 | use std::ffi::CStr; 29 | 30 | let test = cstr!(b"hello\xff"); 31 | assert_eq!(test, CStr::from_bytes_with_nul(b"hello\xff\0").unwrap()); 32 | let test = cstr!("hello"); 33 | assert_eq!(test, CStr::from_bytes_with_nul(b"hello\0").unwrap()); 34 | let test = cstr!(hello); 35 | assert_eq!(test, CStr::from_bytes_with_nul(b"hello\0").unwrap()); 36 | ``` 37 | 38 | 39 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: CI 4 | 5 | jobs: 6 | check: 7 | name: Check 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions-rs/toolchain@v1 12 | with: 13 | profile: minimal 14 | toolchain: stable 15 | override: true 16 | - uses: actions-rs/cargo@v1 17 | with: 18 | command: check 19 | args: --tests 20 | 21 | build_no_std: 22 | name: Build on no_std 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v2 26 | - uses: actions-rs/toolchain@v1 27 | with: 28 | profile: minimal 29 | toolchain: stable 30 | target: thumbv7em-none-eabi 31 | override: true 32 | - uses: actions-rs/cargo@v1 33 | with: 34 | command: build 35 | args: >- 36 | --verbose 37 | --target thumbv7em-none-eabi 38 | --manifest-path tests/test_no_std/Cargo.toml 39 | 40 | test: 41 | name: Test Suite 42 | runs-on: ubuntu-latest 43 | steps: 44 | - uses: actions/checkout@v2 45 | - uses: actions-rs/toolchain@v1 46 | with: 47 | profile: minimal 48 | toolchain: stable 49 | override: true 50 | - uses: actions-rs/cargo@v1 51 | with: 52 | command: test 53 | 54 | fmt: 55 | name: Rustfmt 56 | runs-on: ubuntu-latest 57 | steps: 58 | - uses: actions/checkout@v2 59 | - uses: actions-rs/toolchain@v1 60 | with: 61 | profile: minimal 62 | toolchain: stable 63 | override: true 64 | - run: rustup component add rustfmt 65 | - uses: actions-rs/cargo@v1 66 | with: 67 | command: fmt 68 | args: --all -- --check 69 | 70 | clippy: 71 | name: Clippy 72 | runs-on: ubuntu-latest 73 | steps: 74 | - uses: actions/checkout@v2 75 | - uses: actions-rs/toolchain@v1 76 | with: 77 | profile: minimal 78 | toolchain: stable 79 | override: true 80 | - run: rustup component add clippy 81 | - uses: actions-rs/cargo@v1 82 | with: 83 | command: clippy 84 | args: --tests -- -D warnings 85 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! **This crate has been deprecated. 2 | //! Rust 1.77.0 stabilized [C-string literals][c-string-literal]. 3 | //! From that version, `c"abc"` can be used in place of `cstr!("abc")` provided by this crate. 4 | //! This new feature gives more concise code and faster compilation. 5 | //! Hence, this crate will no longer be maintained.** 6 | //! 7 | //! [c-string-literal]: https://blog.rust-lang.org/2024/03/21/Rust-1.77.0.html#c-string-literals 8 | //! 9 | //! A macro for getting `&'static CStr` from literal or identifier. 10 | //! 11 | //! This macro checks whether the given literal is valid for `CStr` 12 | //! at compile time, and returns a static reference of `CStr`. 13 | //! 14 | //! This macro can be used to initialize constants. 15 | //! 16 | //! ## Example 17 | //! 18 | //! ``` 19 | //! use cstr::cstr; 20 | //! use std::ffi::CStr; 21 | //! 22 | //! let test = cstr!(b"hello\xff"); 23 | //! assert_eq!(test, CStr::from_bytes_with_nul(b"hello\xff\0").unwrap()); 24 | //! let test = cstr!("hello"); 25 | //! assert_eq!(test, CStr::from_bytes_with_nul(b"hello\0").unwrap()); 26 | //! let test = cstr!(hello); 27 | //! assert_eq!(test, CStr::from_bytes_with_nul(b"hello\0").unwrap()); 28 | //! ``` 29 | 30 | // While this isn't necessary when using Cargo >= 1.42, omitting it actually requires path-less 31 | // `--extern proc_macro` to be passed to `rustc` when building this crate. Some tools may not do 32 | // this correctly. So it's added as a precaution. 33 | extern crate proc_macro; 34 | 35 | use crate::parse::parse_input; 36 | use proc_macro::TokenStream as RawTokenStream; 37 | use proc_macro2::{Literal, Span, TokenStream}; 38 | use quote::{quote, quote_spanned}; 39 | use std::ffi::CString; 40 | 41 | mod parse; 42 | 43 | struct Error(Span, &'static str); 44 | 45 | #[proc_macro] 46 | pub fn cstr(input: RawTokenStream) -> RawTokenStream { 47 | let tokens = match build_byte_str(input.into()) { 48 | Ok(s) => quote!(unsafe { ::core::ffi::CStr::from_bytes_with_nul_unchecked(#s) }), 49 | Err(Error(span, msg)) => quote_spanned!(span => compile_error!(#msg)), 50 | }; 51 | tokens.into() 52 | } 53 | 54 | fn build_byte_str(input: TokenStream) -> Result { 55 | let (bytes, span) = parse_input(input)?; 56 | match CString::new(bytes) { 57 | Ok(s) => { 58 | let mut lit = Literal::byte_string(s.as_bytes_with_nul()); 59 | lit.set_span(span); 60 | Ok(lit) 61 | } 62 | Err(_) => Err(Error(span, "nul byte found in the literal")), 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/parse.rs: -------------------------------------------------------------------------------- 1 | use crate::Error; 2 | use proc_macro2::{Delimiter, Ident, Literal, Span, TokenStream, TokenTree}; 3 | use std::char; 4 | 5 | macro_rules! unexpected_content { 6 | () => { 7 | "expected one of: byte string literal, string literal, identifier" 8 | }; 9 | } 10 | 11 | pub(crate) fn parse_input(mut input: TokenStream) -> Result<(Vec, Span), Error> { 12 | loop { 13 | let mut tokens = input.into_iter(); 14 | let token = match tokens.next() { 15 | Some(token) => token, 16 | None => { 17 | return Err(Error( 18 | Span::call_site(), 19 | concat!("unexpected end of input, ", unexpected_content!()), 20 | )) 21 | } 22 | }; 23 | let span = token.span(); 24 | let result = match token { 25 | // Unwrap any empty group which may be created from macro expansion. 26 | TokenTree::Group(group) if group.delimiter() == Delimiter::None => Err(group), 27 | TokenTree::Literal(literal) => match parse_literal(literal) { 28 | Ok(result) => Ok(result), 29 | Err(msg) => return Err(Error(span, msg)), 30 | }, 31 | TokenTree::Ident(ident) => Ok(parse_ident(ident)), 32 | _ => return Err(Error(span, unexpected_content!())), 33 | }; 34 | if let Some(token) = tokens.next() { 35 | return Err(Error(token.span(), "unexpected token")); 36 | } 37 | match result { 38 | Ok(result) => return Ok((result, span)), 39 | Err(group) => input = group.stream(), 40 | } 41 | } 42 | } 43 | 44 | fn parse_literal(literal: Literal) -> Result, &'static str> { 45 | let s = literal.to_string(); 46 | let s = s.as_bytes(); 47 | match s[0] { 48 | b'"' => Ok(parse_cooked_content(s)), 49 | b'r' => Ok(parse_raw_content(&s[1..])), 50 | b'b' => match s[1] { 51 | b'"' => Ok(parse_cooked_content(&s[1..])), 52 | b'r' => Ok(parse_raw_content(&s[2..])), 53 | _ => Err(unexpected_content!()), 54 | }, 55 | _ => Err(unexpected_content!()), 56 | } 57 | } 58 | 59 | fn all_pounds(bytes: &[u8]) -> bool { 60 | bytes.iter().all(|b| *b == b'#') 61 | } 62 | 63 | /// Parses raw string / bytes content after `r` prefix. 64 | fn parse_raw_content(s: &[u8]) -> Vec { 65 | let q_start = s.iter().position(|b| *b == b'"').unwrap(); 66 | let q_end = s.iter().rposition(|b| *b == b'"').unwrap(); 67 | assert!(all_pounds(&s[0..q_start])); 68 | assert!(all_pounds(&s[q_end + 1..q_end + q_start + 1])); 69 | Vec::from(&s[q_start + 1..q_end]) 70 | } 71 | 72 | /// Parses the cooked string / bytes content within quotes. 73 | fn parse_cooked_content(mut s: &[u8]) -> Vec { 74 | s = &s[1..s.iter().rposition(|b| *b == b'"').unwrap()]; 75 | let mut result = Vec::new(); 76 | while !s.is_empty() { 77 | match s[0] { 78 | b'\\' => {} 79 | b'\r' => { 80 | assert_eq!(s[1], b'\n'); 81 | result.push(b'\n'); 82 | s = &s[2..]; 83 | continue; 84 | } 85 | b => { 86 | result.push(b); 87 | s = &s[1..]; 88 | continue; 89 | } 90 | } 91 | let b = s[1]; 92 | s = &s[2..]; 93 | match b { 94 | b'x' => { 95 | let (b, rest) = backslash_x(s); 96 | result.push(b); 97 | s = rest; 98 | } 99 | b'u' => { 100 | let (c, rest) = backslash_u(s); 101 | result.extend_from_slice(c.encode_utf8(&mut [0; 4]).as_bytes()); 102 | s = rest; 103 | } 104 | b'n' => result.push(b'\n'), 105 | b'r' => result.push(b'\r'), 106 | b't' => result.push(b'\t'), 107 | b'\\' => result.push(b'\\'), 108 | b'0' => result.push(b'\0'), 109 | b'\'' => result.push(b'\''), 110 | b'"' => result.push(b'"'), 111 | b'\r' | b'\n' => { 112 | let next = s.iter().position(|b| { 113 | let ch = char::from_u32(u32::from(*b)).unwrap(); 114 | !ch.is_whitespace() 115 | }); 116 | match next { 117 | Some(pos) => s = &s[pos..], 118 | None => s = b"", 119 | } 120 | } 121 | b => panic!("unexpected byte {:?} after \\", b), 122 | } 123 | } 124 | result 125 | } 126 | 127 | fn backslash_x(s: &[u8]) -> (u8, &[u8]) { 128 | let ch = hex_to_u8(s[0]) * 0x10 + hex_to_u8(s[1]); 129 | (ch, &s[2..]) 130 | } 131 | 132 | fn hex_to_u8(b: u8) -> u8 { 133 | match b { 134 | b'0'..=b'9' => b - b'0', 135 | b'a'..=b'f' => b - b'a' + 10, 136 | b'A'..=b'F' => b - b'A' + 10, 137 | _ => unreachable!("unexpected non-hex character {:?} after \\x", b), 138 | } 139 | } 140 | 141 | fn backslash_u(s: &[u8]) -> (char, &[u8]) { 142 | assert_eq!(s[0], b'{'); 143 | let end = s[1..].iter().position(|b| *b == b'}').unwrap(); 144 | let mut ch = 0; 145 | for b in &s[1..=end] { 146 | ch *= 0x10; 147 | ch += u32::from(hex_to_u8(*b)); 148 | } 149 | (char::from_u32(ch).unwrap(), &s[end + 2..]) 150 | } 151 | 152 | fn parse_ident(ident: Ident) -> Vec { 153 | ident.to_string().into_bytes() 154 | } 155 | 156 | #[cfg(test)] 157 | mod tests { 158 | use super::*; 159 | use std::str::FromStr; 160 | 161 | // Tests below were modified from 162 | // https://github.com/dtolnay/syn/blob/cd5fdc0f530f822446fccaf831669cd0cf4a0fc9/tests/test_lit.rs 163 | 164 | fn lit(s: &str) -> Vec { 165 | match TokenStream::from_str(s) 166 | .unwrap() 167 | .into_iter() 168 | .next() 169 | .unwrap() 170 | { 171 | TokenTree::Literal(lit) => parse_literal(lit).unwrap(), 172 | _ => panic!(), 173 | } 174 | } 175 | 176 | #[test] 177 | fn strings() { 178 | #[track_caller] 179 | fn test_string(s: &str, value: &[u8]) { 180 | assert_eq!(lit(s), value); 181 | } 182 | 183 | test_string("\"a\"", b"a"); 184 | test_string("\"\\n\"", b"\n"); 185 | test_string("\"\\r\"", b"\r"); 186 | test_string("\"\\t\"", b"\t"); 187 | test_string("\"🐕\"", b"\xf0\x9f\x90\x95"); // NOTE: This is an emoji 188 | test_string("\"\\\"\"", b"\""); 189 | test_string("\"'\"", b"'"); 190 | test_string("\"\"", b""); 191 | test_string("\"\\u{1F415}\"", b"\xf0\x9f\x90\x95"); 192 | test_string( 193 | "\"contains\nnewlines\\\nescaped newlines\"", 194 | b"contains\nnewlinesescaped newlines", 195 | ); 196 | test_string("r\"raw\nstring\\\nhere\"", b"raw\nstring\\\nhere"); 197 | test_string("\"...\"q", b"..."); 198 | test_string("r\"...\"q", b"..."); 199 | test_string("r##\"...\"##q", b"..."); 200 | } 201 | 202 | #[test] 203 | fn byte_strings() { 204 | #[track_caller] 205 | fn test_byte_string(s: &str, value: &[u8]) { 206 | assert_eq!(lit(s), value); 207 | } 208 | 209 | test_byte_string("b\"a\"", b"a"); 210 | test_byte_string("b\"\\n\"", b"\n"); 211 | test_byte_string("b\"\\r\"", b"\r"); 212 | test_byte_string("b\"\\t\"", b"\t"); 213 | test_byte_string("b\"\\\"\"", b"\""); 214 | test_byte_string("b\"'\"", b"'"); 215 | test_byte_string("b\"\"", b""); 216 | test_byte_string( 217 | "b\"contains\nnewlines\\\nescaped newlines\"", 218 | b"contains\nnewlinesescaped newlines", 219 | ); 220 | test_byte_string("br\"raw\nstring\\\nhere\"", b"raw\nstring\\\nhere"); 221 | test_byte_string("b\"...\"q", b"..."); 222 | test_byte_string("br\"...\"q", b"..."); 223 | test_byte_string("br##\"...\"##q", b"..."); 224 | } 225 | } 226 | --------------------------------------------------------------------------------