forked from libre-chip/cpu
		
	add name_mangling_serde crate
This commit is contained in:
		
							parent
							
								
									60341e22af
								
							
						
					
					
						commit
						9199fdf35c
					
				
					 6 changed files with 502 additions and 0 deletions
				
			
		
							
								
								
									
										8
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							|  | @ -479,6 +479,14 @@ version = "2.7.4" | |||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "name_mangling_serde" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "serde", | ||||
|  "serde_json", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "num-bigint" | ||||
| version = "0.4.6" | ||||
|  |  | |||
|  | @ -15,6 +15,8 @@ rust-version = "1.82.0" | |||
| 
 | ||||
| [workspace.dependencies] | ||||
| fayalite = { git = "https://git.libre-chip.org/libre-chip/fayalite.git", version = "0.3.0", branch = "master" } | ||||
| serde = { version = "1.0.202", features = ["derive"] } | ||||
| serde_json = { version = "1.0.117", features = ["preserve_order"] } | ||||
| 
 | ||||
| [profile.dev] | ||||
| opt-level = 1 | ||||
|  |  | |||
							
								
								
									
										20
									
								
								crates/name_mangling_serde/Cargo.toml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								crates/name_mangling_serde/Cargo.toml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | |||
| # SPDX-License-Identifier: LGPL-3.0-or-later | ||||
| # See Notices.txt for copyright information | ||||
| [package] | ||||
| name = "name_mangling_serde" | ||||
| description = "serde serializer/deserializer for name mangling" | ||||
| workspace = "../.." | ||||
| readme = "README.md" | ||||
| publish = false | ||||
| edition.workspace = true | ||||
| license.workspace = true | ||||
| repository.workspace = true | ||||
| rust-version.workspace = true | ||||
| version.workspace = true | ||||
| 
 | ||||
| [dependencies] | ||||
| serde.workspace = true | ||||
| serde_json.workspace = true | ||||
| 
 | ||||
| [lints.rust] | ||||
| unexpected_cfgs = { level = "warn", check-cfg = ['cfg(todo)'] } | ||||
							
								
								
									
										1
									
								
								crates/name_mangling_serde/LICENSE.md
									
										
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								crates/name_mangling_serde/LICENSE.md
									
										
									
									
									
										Symbolic link
									
								
							|  | @ -0,0 +1 @@ | |||
| ../../LICENSE.md | ||||
							
								
								
									
										1
									
								
								crates/name_mangling_serde/Notices.txt
									
										
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								crates/name_mangling_serde/Notices.txt
									
										
									
									
									
										Symbolic link
									
								
							|  | @ -0,0 +1 @@ | |||
| ../../Notices.txt | ||||
							
								
								
									
										470
									
								
								crates/name_mangling_serde/src/lib.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										470
									
								
								crates/name_mangling_serde/src/lib.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,470 @@ | |||
| // SPDX-License-Identifier: LGPL-3.0-or-later
 | ||||
| // See Notices.txt for copyright information
 | ||||
| 
 | ||||
| use serde::{de::DeserializeOwned, Serialize}; | ||||
| use serde_json::{Map, Number, Value}; | ||||
| use std::{ | ||||
|     fmt::{self, Write}, | ||||
|     num::ParseIntError, | ||||
| }; | ||||
| 
 | ||||
| macro_rules! byte_enum { | ||||
|     ( | ||||
|         #[repr(u8)] | ||||
|         $(#[$meta:meta])* | ||||
|         $vis:vis enum $enum:ident { | ||||
|             $($Variant:ident = $value:expr,)* | ||||
|         } | ||||
|     ) => { | ||||
|         #[repr(u8)] | ||||
|         $(#[$meta])* | ||||
|         $vis enum $enum { | ||||
|             $($Variant = $value,)* | ||||
|         } | ||||
| 
 | ||||
|         impl $enum { | ||||
|             $vis fn new(v: u8) -> Option<Self> { | ||||
|                 struct Values; | ||||
|                 #[allow(non_upper_case_globals)] | ||||
|                 impl Values { | ||||
|                     $(const $Variant: u8 = $enum::$Variant as u8;)* | ||||
|                 } | ||||
|                 match v { | ||||
|                     $(Values::$Variant => Some(Self::$Variant),)* | ||||
|                     _ => None, | ||||
|                 } | ||||
|             } | ||||
|             #[allow(dead_code)] | ||||
|             $vis fn as_char(self) -> char { | ||||
|                 const { | ||||
|                     $(assert!((Self::$Variant as u8).is_ascii());)* | ||||
|                 }; | ||||
|                 self as u8 as char | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| macro_rules! string_escapes { | ||||
|     ( | ||||
|         $key_vis:vis enum $StringEscapeKey:ident {} | ||||
|         $value_vis:vis enum $StringEscapeValue:ident { | ||||
|             $( | ||||
|                 #[key = $key:expr] | ||||
|                 $Variant:ident = $value:expr, | ||||
|             )* | ||||
|         } | ||||
|     ) => { | ||||
|         byte_enum! { | ||||
|             #[repr(u8)] | ||||
|             #[derive(Clone, Copy, Debug)] | ||||
|             $key_vis enum $StringEscapeKey { | ||||
|                 $($Variant = $key,)* | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         byte_enum! { | ||||
|             #[repr(u8)] | ||||
|             #[derive(Clone, Copy, Debug)] | ||||
|             $value_vis enum $StringEscapeValue { | ||||
|                 $($Variant = $value,)* | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         impl From<$StringEscapeKey> for $StringEscapeValue { | ||||
|             fn from(v: $StringEscapeKey) -> Self { | ||||
|                 match v { | ||||
|                     $($StringEscapeKey::$Variant => Self::$Variant,)* | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         impl From<$StringEscapeValue> for $StringEscapeKey { | ||||
|             fn from(v: $StringEscapeValue) -> Self { | ||||
|                 match v { | ||||
|                     $($StringEscapeValue::$Variant => Self::$Variant,)* | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| string_escapes! { | ||||
|     enum StringEscapeKey {} | ||||
|     enum StringEscapeValue { | ||||
|         #[key = b's'] | ||||
|         Space = b' ', | ||||
|         #[key = b't'] | ||||
|         Tab = b'\t', | ||||
|         #[key = b'r'] | ||||
|         CR = b'\r', | ||||
|         #[key = b'n'] | ||||
|         NewLine = b'\n', | ||||
|         #[key = b'_'] | ||||
|         Underline = b'_', | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn json_string_to_name_part(value: &str, out: &mut String) { | ||||
|     out.push(ValuePrefix::String.as_char()); | ||||
|     write!(out, "{}_", value.len()).unwrap(); | ||||
|     for b in value.bytes() { | ||||
|         if let Some(v) = StringEscapeValue::new(b) { | ||||
|             out.push('_'); | ||||
|             out.push(StringEscapeKey::from(v).as_char()); | ||||
|         } else if b.is_ascii_alphanumeric() { | ||||
|             out.push(b as char); | ||||
|         } else { | ||||
|             write!(out, "_{b:02x}").unwrap() | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| byte_enum! { | ||||
|     #[repr(u8)] | ||||
|     #[derive(Clone, Copy, Debug)] | ||||
|     enum ValuePrefix { | ||||
|         Null = b'z', | ||||
|         False = b'f', | ||||
|         True = b't', | ||||
|         Number = b'n', | ||||
|         String = b's', | ||||
|         Array = b'a', | ||||
|         Object = b'o', | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn json_value_to_name_part(value: &Value, out: &mut String) { | ||||
|     match value { | ||||
|         Value::Null => out.push(ValuePrefix::Null.as_char()), | ||||
|         Value::Bool(false) => out.push(ValuePrefix::False.as_char()), | ||||
|         Value::Bool(true) => out.push(ValuePrefix::True.as_char()), | ||||
|         Value::Number(number) => { | ||||
|             out.push(ValuePrefix::Number.as_char()); | ||||
|             let start = out.len(); | ||||
|             write!(out, "{number}").unwrap(); | ||||
|             for i in start..out.len() { | ||||
|                 out.replace_range( | ||||
|                     i..=i, | ||||
|                     match out.as_bytes()[i] { | ||||
|                         b'0'..=b'9' => continue, | ||||
|                         b'+' => "", | ||||
|                         b'-' => "n", | ||||
|                         b'.' => "p", | ||||
|                         b'e' | b'E' => "e", | ||||
|                         _ => unreachable!("invalid character in JSON number"), | ||||
|                     }, | ||||
|                 ); | ||||
|             } | ||||
|         } | ||||
|         Value::String(string) => json_string_to_name_part(string, out), | ||||
|         Value::Array(array) => { | ||||
|             out.push(ValuePrefix::Array.as_char()); | ||||
|             write!(out, "{}", array.len()).unwrap(); | ||||
|             for element in array { | ||||
|                 json_value_to_name_part(element, out); | ||||
|             } | ||||
|         } | ||||
|         Value::Object(object) => { | ||||
|             out.push(ValuePrefix::Object.as_char()); | ||||
|             write!(out, "{}", object.len()).unwrap(); | ||||
|             for (k, v) in object { | ||||
|                 json_string_to_name_part(k, out); | ||||
|                 json_value_to_name_part(v, out); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub const NAME_PREFIX: &str = "__HDL"; | ||||
| 
 | ||||
| pub fn json_value_to_name(value: &Value) -> String { | ||||
|     let mut retval = NAME_PREFIX.into(); | ||||
|     json_value_to_name_part(value, &mut retval); | ||||
|     retval | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
| pub enum Error { | ||||
|     Serde(serde_json::Error), | ||||
|     NameDoesNotStartWithKnownPrefix, | ||||
|     UnknownValuePrefix, | ||||
|     MissingValuePrefix, | ||||
|     InvalidLength(ParseIntError), | ||||
|     TrailingCharacters, | ||||
|     KeyMustBeAString, | ||||
|     StringMissingUnderline, | ||||
|     StringTruncated, | ||||
|     InvalidEscape, | ||||
|     InvalidString, | ||||
| } | ||||
| 
 | ||||
| impl From<serde_json::Error> for Error { | ||||
|     fn from(value: serde_json::Error) -> Self { | ||||
|         Self::Serde(value) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl fmt::Display for Error { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||
|         match self { | ||||
|             Self::Serde(e) => e.fmt(f), | ||||
|             Self::NameDoesNotStartWithKnownPrefix => { | ||||
|                 f.write_str("name does not start with the known prefix") | ||||
|             } | ||||
|             Self::UnknownValuePrefix => f.write_str("unknown value prefix"), | ||||
|             Self::MissingValuePrefix => f.write_str("missing value prefix"), | ||||
|             Self::InvalidLength(_) => f.write_str("invalid length"), | ||||
|             Self::TrailingCharacters => f.write_str("trailing characters"), | ||||
|             Self::KeyMustBeAString => f.write_str("key must be a string"), | ||||
|             Self::StringMissingUnderline => f.write_str("string missing `_` after length"), | ||||
|             Self::StringTruncated => f.write_str("string truncated"), | ||||
|             Self::InvalidEscape => f.write_str("invalid escape"), | ||||
|             Self::InvalidString => f.write_str("invalid string"), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl std::error::Error for Error { | ||||
|     fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { | ||||
|         match self { | ||||
|             Self::Serde(e) => e.source(), | ||||
|             Self::NameDoesNotStartWithKnownPrefix => None, | ||||
|             Self::UnknownValuePrefix => None, | ||||
|             Self::MissingValuePrefix => None, | ||||
|             Self::InvalidLength(e) => Some(e), | ||||
|             Self::TrailingCharacters => None, | ||||
|             Self::KeyMustBeAString => None, | ||||
|             Self::StringMissingUnderline => None, | ||||
|             Self::StringTruncated => None, | ||||
|             Self::InvalidEscape => None, | ||||
|             Self::InvalidString => None, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| struct NameParser<'a> { | ||||
|     name_part: &'a str, | ||||
|     number_buf: String, | ||||
| } | ||||
| 
 | ||||
| impl NameParser<'_> { | ||||
|     fn parse_len(&mut self) -> Result<usize, Error> { | ||||
|         let len_end = self | ||||
|             .name_part | ||||
|             .bytes() | ||||
|             .position(|b| !b.is_ascii_digit()) | ||||
|             .unwrap_or(self.name_part.len()); | ||||
|         let (len, rest) = self.name_part.split_at(len_end); | ||||
|         self.name_part = rest; | ||||
|         len.parse().map_err(Error::InvalidLength) | ||||
|     } | ||||
|     fn parse_string_without_prefix(&mut self) -> Result<String, Error> { | ||||
|         let len = self.parse_len()?; | ||||
|         let Some(rest) = self.name_part.strip_prefix("_") else { | ||||
|             return Err(Error::StringMissingUnderline); | ||||
|         }; | ||||
|         self.name_part = rest; | ||||
|         let mut bytes = Vec::new(); | ||||
|         for _ in 0..len { | ||||
|             let b = self | ||||
|                 .name_part | ||||
|                 .bytes() | ||||
|                 .next() | ||||
|                 .ok_or(Error::StringTruncated)?; | ||||
|             if b.is_ascii_alphanumeric() { | ||||
|                 bytes.push(b); | ||||
|                 self.name_part = &self.name_part[1..]; | ||||
|             } else if b == b'_' { | ||||
|                 self.name_part = &self.name_part[1..]; | ||||
|                 let escape = self.name_part.bytes().next().ok_or(Error::InvalidEscape)?; | ||||
|                 self.name_part = &self.name_part[1..]; | ||||
|                 if let Some(high) = (escape as char).to_digit(16) { | ||||
|                     let low = self | ||||
|                         .name_part | ||||
|                         .bytes() | ||||
|                         .next() | ||||
|                         .ok_or(Error::StringTruncated)?; | ||||
|                     let low = (low as char).to_digit(16).ok_or(Error::InvalidString)?; | ||||
|                     self.name_part = &self.name_part[1..]; | ||||
|                     bytes.push((high * 16 + low) as u8); | ||||
|                 } else { | ||||
|                     let escape = StringEscapeKey::new(escape).ok_or(Error::InvalidEscape)?; | ||||
|                     bytes.push(StringEscapeValue::from(escape) as u8); | ||||
|                 } | ||||
|             } else if let Some(high) = (b as char).to_digit(16) { | ||||
|                 self.name_part = &self.name_part[1..]; | ||||
|                 let low = self | ||||
|                     .name_part | ||||
|                     .bytes() | ||||
|                     .next() | ||||
|                     .ok_or(Error::StringTruncated)?; | ||||
|                 let low = (low as char).to_digit(16).ok_or(Error::InvalidString)?; | ||||
|                 self.name_part = &self.name_part[1..]; | ||||
|                 bytes.push((high * 16 + low) as u8); | ||||
|             } else { | ||||
|                 return Err(Error::InvalidString); | ||||
|             } | ||||
|         } | ||||
|         String::from_utf8(bytes).map_err(|_| Error::InvalidString) | ||||
|     } | ||||
|     fn parse_string(&mut self) -> Result<String, Error> { | ||||
|         if let ValuePrefix::String = self.parse_value_prefix()? { | ||||
|             self.parse_string_without_prefix() | ||||
|         } else { | ||||
|             Err(Error::KeyMustBeAString) | ||||
|         } | ||||
|     } | ||||
|     fn parse_number_without_prefix(&mut self) -> Result<Number, Error> { | ||||
|         let mut bytes = self.name_part.as_bytes().iter(); | ||||
|         self.number_buf.clear(); | ||||
|         if let Some(b'n') = bytes.clone().next() { | ||||
|             bytes.next(); | ||||
|             self.number_buf.push('-'); | ||||
|         } | ||||
|         while let Some(&b @ b'0'..=b'9') = bytes.clone().next() { | ||||
|             bytes.next(); | ||||
|             self.number_buf.push(b as char); | ||||
|         } | ||||
|         if let Some(b'p') = bytes.clone().next() { | ||||
|             bytes.next(); | ||||
|             self.number_buf.push('.'); | ||||
|             while let Some(&b @ b'0'..=b'9') = bytes.clone().next() { | ||||
|                 bytes.next(); | ||||
|                 self.number_buf.push(b as char); | ||||
|             } | ||||
|         } | ||||
|         if let Some(b'e') = bytes.clone().next() { | ||||
|             bytes.next(); | ||||
|             self.number_buf.push('e'); | ||||
|             if let Some(b'n') = bytes.clone().next() { | ||||
|                 bytes.next(); | ||||
|                 self.number_buf.push('-'); | ||||
|             } | ||||
|             while let Some(&b @ b'0'..=b'9') = bytes.clone().next() { | ||||
|                 bytes.next(); | ||||
|                 self.number_buf.push(b as char); | ||||
|             } | ||||
|         } | ||||
|         self.name_part = &self.name_part[self.name_part.len() - bytes.len()..]; | ||||
|         Ok(self.number_buf.parse()?) | ||||
|     } | ||||
|     fn parse_value_prefix(&mut self) -> Result<ValuePrefix, Error> { | ||||
|         let value_prefix = self | ||||
|             .name_part | ||||
|             .bytes() | ||||
|             .next() | ||||
|             .ok_or(Error::MissingValuePrefix)?; | ||||
|         let value_prefix = ValuePrefix::new(value_prefix).ok_or(Error::UnknownValuePrefix)?; | ||||
|         self.name_part = &self.name_part[1..]; | ||||
|         Ok(value_prefix) | ||||
|     } | ||||
|     fn parse_value(&mut self) -> Result<Value, Error> { | ||||
|         Ok(match self.parse_value_prefix()? { | ||||
|             ValuePrefix::Null => Value::Null, | ||||
|             ValuePrefix::False => Value::Bool(false), | ||||
|             ValuePrefix::True => Value::Bool(true), | ||||
|             ValuePrefix::Number => Value::Number(self.parse_number_without_prefix()?), | ||||
|             ValuePrefix::String => Value::String(self.parse_string_without_prefix()?), | ||||
|             ValuePrefix::Array => { | ||||
|                 let len = self.parse_len()?; | ||||
|                 let mut array = Vec::new(); | ||||
|                 for _ in 0..len { | ||||
|                     array.push(self.parse_value()?); | ||||
|                 } | ||||
|                 Value::Array(array) | ||||
|             } | ||||
|             ValuePrefix::Object => { | ||||
|                 let len = self.parse_len()?; | ||||
|                 let mut object = Map::new(); | ||||
|                 for _ in 0..len { | ||||
|                     let key = self.parse_string()?; | ||||
|                     let value = self.parse_value()?; | ||||
|                     object.insert(key, value); | ||||
|                 } | ||||
|                 Value::Object(object) | ||||
|             } | ||||
|         }) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub fn name_to_json_value(name: &str) -> Result<Value, Error> { | ||||
|     let Some(name_part) = name.strip_prefix(NAME_PREFIX) else { | ||||
|         return Err(Error::NameDoesNotStartWithKnownPrefix); | ||||
|     }; | ||||
|     let mut parser = NameParser { | ||||
|         name_part, | ||||
|         number_buf: String::new(), | ||||
|     }; | ||||
|     let retval = parser.parse_value()?; | ||||
|     if !parser.name_part.is_empty() { | ||||
|         Err(Error::TrailingCharacters) | ||||
|     } else { | ||||
|         Ok(retval) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub fn from_name<T: DeserializeOwned>(name: &str) -> Result<T, Error> { | ||||
|     Ok(serde_json::from_value(name_to_json_value(name)?)?) | ||||
| } | ||||
| 
 | ||||
| pub fn to_name<T: Serialize>(value: T) -> Result<String, Error> { | ||||
|     Ok(json_value_to_name(&serde_json::to_value(value)?)) | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use serde_json::json; | ||||
| 
 | ||||
|     use super::*; | ||||
| 
 | ||||
|     #[test] | ||||
|     fn test_from_to_name() { | ||||
|         #[track_caller] | ||||
|         fn check_from_to_name(value: Value, name: &str) { | ||||
|             assert_eq!(name, json_value_to_name(&value)); | ||||
|             assert_eq!( | ||||
|                 Ok(value), | ||||
|                 name_to_json_value(name).map_err(|e| e.to_string()) | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         check_from_to_name(json! { null }, "__HDLz"); | ||||
|         check_from_to_name(json! { false }, "__HDLf"); | ||||
|         check_from_to_name(json! { true }, "__HDLt"); | ||||
|         check_from_to_name(json! { 0 }, "__HDLn0"); | ||||
|         check_from_to_name(json! { 0.1 }, "__HDLn0p1"); | ||||
|         check_from_to_name(json! { -0.1 }, "__HDLnn0p1"); | ||||
|         check_from_to_name(json! { 1234567 }, "__HDLn1234567"); | ||||
|         check_from_to_name(json! { -1.2345678e-20 }, "__HDLnn1p2345678en20"); | ||||
|         check_from_to_name(json! { -1.2345e300 }, "__HDLnn1p2345e300"); | ||||
|         check_from_to_name(json! { -5 }, "__HDLnn5"); | ||||
|         check_from_to_name(json! { "" }, "__HDLs0_"); | ||||
|         check_from_to_name(json! { "a" }, "__HDLs1_a"); | ||||
|         check_from_to_name(json! { "A" }, "__HDLs1_A"); | ||||
|         check_from_to_name(json! { "z" }, "__HDLs1_z"); | ||||
|         check_from_to_name(json! { "Z" }, "__HDLs1_Z"); | ||||
|         check_from_to_name(json! { "0" }, "__HDLs1_0"); | ||||
|         check_from_to_name(json! { "9" }, "__HDLs1_9"); | ||||
|         check_from_to_name(json! { "_" }, "__HDLs1___"); | ||||
|         check_from_to_name(json! { " " }, "__HDLs1__s"); | ||||
|         check_from_to_name(json! { "\t" }, "__HDLs1__t"); | ||||
|         check_from_to_name(json! { "\r" }, "__HDLs1__r"); | ||||
|         check_from_to_name(json! { "\n" }, "__HDLs1__n"); | ||||
|         check_from_to_name(json! { "\u{25}" }, "__HDLs1__25"); | ||||
|         check_from_to_name(json! { "\u{100}" }, "__HDLs2__c4_80"); | ||||
|         check_from_to_name(json! { "\u{1000}" }, "__HDLs3__e1_80_80"); | ||||
|         check_from_to_name(json! { "\u{10000}" }, "__HDLs4__f0_90_80_80"); | ||||
|         check_from_to_name(json! { "foo" }, "__HDLs3_foo"); | ||||
|         check_from_to_name(json! { { "foo": 123 } }, "__HDLo1s3_foon123"); | ||||
|         check_from_to_name( | ||||
|             json! { { "foo": 123, "bar": null } }, | ||||
|             "__HDLo2s3_foon123s3_barz", | ||||
|         ); | ||||
|         check_from_to_name(json! { [1, 2, 3, 4] }, "__HDLa4n1n2n3n4"); | ||||
|         check_from_to_name( | ||||
|             json! { { "a": [], "b": null, "c": 1234, "d": {} } }, | ||||
|             "__HDLo4s1_aa0s1_bzs1_cn1234s1_do0", | ||||
|         ); | ||||
|     } | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue