1use crate::xdr::SCSYMBOL_LIMIT;
52use crate::{
53 declare_tag_based_small_and_object_wrappers, val::ValConvert, Compare, ConversionError, Env,
54 Tag, TryFromVal, Val,
55};
56use core::{cmp::Ordering, fmt::Debug, hash::Hash, str};
57
58declare_tag_based_small_and_object_wrappers!(Symbol, SymbolSmall, SymbolObject);
59
60#[derive(Debug)]
62pub enum SymbolError {
63 TooLong(usize),
66 BadChar(u8),
69 MalformedSmall,
71}
72
73impl core::fmt::Display for SymbolError {
74 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
75 match self {
76 SymbolError::TooLong(len) => f.write_fmt(format_args!(
77 "symbol too long: length {len}, max {MAX_SMALL_CHARS}"
78 )),
79 SymbolError::BadChar(char) => f.write_fmt(format_args!(
80 "symbol bad char: encountered `{char}`, supported range [a-zA-Z0-9_]"
81 )),
82 SymbolError::MalformedSmall => f.write_str("malformed small symbol"),
83 }
84 }
85}
86
87impl From<SymbolError> for ConversionError {
88 fn from(_: SymbolError) -> Self {
89 ConversionError
90 }
91}
92
93extern crate static_assertions as sa;
94
95use super::val::BODY_BITS;
96
97pub(crate) const MAX_SMALL_CHARS: usize = 9;
100const CODE_BITS: usize = 6;
101const CODE_MASK: u64 = (1u64 << CODE_BITS) - 1;
102const SMALL_MASK: u64 = (1u64 << (MAX_SMALL_CHARS * CODE_BITS)) - 1;
103sa::const_assert!(CODE_MASK == 0x3f);
104sa::const_assert!(CODE_BITS * MAX_SMALL_CHARS + 2 == BODY_BITS);
105sa::const_assert!(SMALL_MASK == 0x003f_ffff_ffff_ffff);
106
107impl<E: Env> TryFromVal<E, &[u8]> for Symbol {
108 type Error = crate::Error;
109
110 fn try_from_val(env: &E, v: &&[u8]) -> Result<Self, Self::Error> {
111 if let Ok(s) = SymbolSmall::try_from_bytes(v) {
115 Ok(s.into())
116 } else {
117 env.symbol_new_from_slice(v)
118 .map(Into::into)
119 .map_err(Into::into)
120 }
121 }
122}
123
124impl<E: Env> TryFromVal<E, &str> for Symbol {
125 type Error = crate::Error;
126
127 fn try_from_val(env: &E, v: &&str) -> Result<Self, Self::Error> {
128 Symbol::try_from_val(env, &v.as_bytes())
129 }
130}
131
132impl<E: Env> Compare<Symbol> for E {
133 type Error = E::Error;
134 fn compare(&self, a: &Symbol, b: &Symbol) -> Result<Ordering, Self::Error> {
135 let taga = a.0.get_tag();
136 let tagb = b.0.get_tag();
137 match taga.cmp(&tagb) {
138 Ordering::Equal => {
139 if taga == Tag::SymbolSmall {
140 let ssa = unsafe { SymbolSmall::unchecked_from_val(a.0) };
141 let ssb = unsafe { SymbolSmall::unchecked_from_val(b.0) };
142 Ok(ssa.cmp(&ssb))
143 } else {
144 let soa = unsafe { SymbolObject::unchecked_from_val(a.0) };
145 let sob = unsafe { SymbolObject::unchecked_from_val(b.0) };
146 self.compare(&soa, &sob)
147 }
148 }
149 other => Ok(other),
150 }
151 }
152}
153
154impl SymbolSmall {
155 #[doc(hidden)]
156 pub const fn try_from_body(body: u64) -> Result<Self, SymbolError> {
157 if body & SMALL_MASK != body {
161 Err(SymbolError::MalformedSmall)
162 } else {
163 Ok(unsafe { SymbolSmall::from_body(body) })
164 }
165 }
166}
167
168impl Symbol {
169 pub const fn try_from_small_bytes(b: &[u8]) -> Result<Self, SymbolError> {
170 match SymbolSmall::try_from_bytes(b) {
171 Ok(sym) => Ok(Symbol(sym.0)),
172 Err(e) => Err(e),
173 }
174 }
175
176 pub const fn try_from_small_str(s: &str) -> Result<Self, SymbolError> {
177 Self::try_from_small_bytes(s.as_bytes())
178 }
179
180 #[cfg(feature = "testutils")]
182 pub const fn from_small_str(s: &str) -> Self {
183 Symbol(SymbolSmall::from_str(s).0)
184 }
185}
186
187impl Ord for SymbolSmall {
188 fn cmp(&self, other: &Self) -> Ordering {
189 Iterator::cmp(self.into_iter(), *other)
190 }
191}
192
193impl TryFrom<&[u8]> for SymbolSmall {
194 type Error = SymbolError;
195
196 fn try_from(b: &[u8]) -> Result<SymbolSmall, SymbolError> {
197 Self::try_from_bytes(b)
198 }
199}
200
201#[cfg(feature = "std")]
202use crate::xdr::StringM;
203#[cfg(feature = "std")]
204impl<const N: u32> TryFrom<StringM<N>> for SymbolSmall {
205 type Error = SymbolError;
206
207 fn try_from(v: StringM<N>) -> Result<Self, Self::Error> {
208 v.as_slice().try_into()
209 }
210}
211#[cfg(feature = "std")]
212impl<const N: u32> TryFrom<&StringM<N>> for SymbolSmall {
213 type Error = SymbolError;
214
215 fn try_from(v: &StringM<N>) -> Result<Self, Self::Error> {
216 v.as_slice().try_into()
217 }
218}
219
220impl SymbolSmall {
221 #[doc(hidden)]
222 pub const fn validate_byte(b: u8) -> Result<(), SymbolError> {
223 match Self::encode_byte(b) {
224 Ok(_) => Ok(()),
225 Err(e) => Err(e),
226 }
227 }
228
229 const fn encode_byte(b: u8) -> Result<u8, SymbolError> {
230 let v = match b {
231 b'_' => 1,
232 b'0'..=b'9' => 2 + (b - b'0'),
233 b'A'..=b'Z' => 12 + (b - b'A'),
234 b'a'..=b'z' => 38 + (b - b'a'),
235 _ => return Err(SymbolError::BadChar(b)),
236 };
237 Ok(v)
238 }
239
240 pub const fn try_from_bytes(bytes: &[u8]) -> Result<SymbolSmall, SymbolError> {
241 if bytes.len() > MAX_SMALL_CHARS {
242 return Err(SymbolError::TooLong(bytes.len()));
243 }
244 let mut n = 0;
245 let mut accum: u64 = 0;
246 while n < bytes.len() {
247 let v = match Self::encode_byte(bytes[n]) {
248 Ok(v) => v,
249 Err(e) => return Err(e),
250 };
251 accum <<= CODE_BITS;
252 accum |= v as u64;
253 n += 1;
254 }
255 Ok(unsafe { Self::from_body(accum) })
256 }
257
258 pub const fn try_from_str(s: &str) -> Result<SymbolSmall, SymbolError> {
259 Self::try_from_bytes(s.as_bytes())
260 }
261
262 #[doc(hidden)]
263 pub const unsafe fn get_body(&self) -> u64 {
264 self.0.get_body()
265 }
266
267 #[cfg(feature = "testutils")]
269 pub const fn from_str(s: &str) -> SymbolSmall {
270 match Self::try_from_str(s) {
271 Ok(sym) => sym,
272 Err(SymbolError::TooLong(_)) => panic!("symbol too long"),
273 Err(SymbolError::BadChar(_)) => panic!("symbol bad char"),
274 Err(SymbolError::MalformedSmall) => panic!("malformed small symbol"),
275 }
276 }
277
278 pub fn to_str(&self) -> SymbolStr {
279 sa::const_assert!(SCSYMBOL_LIMIT as usize >= MAX_SMALL_CHARS);
280 let mut chars = [b'\x00'; SCSYMBOL_LIMIT as usize];
281 for (src, dst) in self.into_iter().zip(chars.iter_mut()) {
282 *dst = src as u8
283 }
284 SymbolStr(chars)
285 }
286}
287
288#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
293pub struct SymbolStr([u8; SCSYMBOL_LIMIT as usize]);
294
295impl SymbolStr {
296 pub fn is_empty(&self) -> bool {
297 self.0[0] == 0
298 }
299 pub fn len(&self) -> usize {
300 let s: &[u8] = &self.0;
301 for (i, x) in s.iter().enumerate() {
302 if *x == 0 {
303 return i;
304 }
305 }
306 s.len()
307 }
308}
309
310impl Debug for SymbolStr {
311 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
312 let s: &str = self.as_ref();
313 f.debug_tuple("SymbolStr").field(&s).finish()
314 }
315}
316
317impl AsRef<[u8]> for SymbolStr {
318 fn as_ref(&self) -> &[u8] {
319 let s: &[u8] = &self.0;
320 &s[..self.len()]
321 }
322}
323
324impl AsRef<str> for SymbolStr {
328 fn as_ref(&self) -> &str {
329 let s: &[u8] = self.as_ref();
330 unsafe { str::from_utf8_unchecked(s) }
331 }
332}
333
334impl From<&SymbolSmall> for SymbolStr {
335 fn from(s: &SymbolSmall) -> Self {
336 s.to_str()
337 }
338}
339
340impl From<SymbolSmall> for SymbolStr {
341 fn from(s: SymbolSmall) -> Self {
342 (&s).into()
343 }
344}
345
346impl<E: Env> TryFromVal<E, Symbol> for SymbolStr {
347 type Error = crate::Error;
348
349 fn try_from_val(env: &E, v: &Symbol) -> Result<Self, Self::Error> {
350 if let Ok(ss) = SymbolSmall::try_from(*v) {
351 Ok(ss.into())
352 } else {
353 let obj: SymbolObject = unsafe { SymbolObject::unchecked_from_val(v.0) };
354 let mut arr = [0u8; SCSYMBOL_LIMIT as usize];
355 let len: u32 = env.symbol_len(obj).map_err(Into::into)?.into();
356 if let Some(slice) = arr.get_mut(..len as usize) {
357 env.symbol_copy_to_slice(obj, Val::U32_ZERO, slice)
358 .map_err(Into::into)?;
359 Ok(SymbolStr(arr))
360 } else {
361 Err(crate::Error::from_type_and_code(
362 crate::xdr::ScErrorType::Value,
363 crate::xdr::ScErrorCode::InternalError,
364 ))
365 }
366 }
367 }
368}
369
370#[cfg(feature = "std")]
371impl From<SymbolSmall> for String {
372 fn from(s: SymbolSmall) -> Self {
373 s.to_string()
374 }
375}
376#[cfg(feature = "std")]
377impl From<SymbolStr> for String {
378 fn from(s: SymbolStr) -> Self {
379 s.to_string()
380 }
381}
382#[cfg(feature = "std")]
383impl ToString for SymbolSmall {
384 fn to_string(&self) -> String {
385 self.into_iter().collect()
386 }
387}
388#[cfg(feature = "std")]
389impl ToString for SymbolStr {
390 fn to_string(&self) -> String {
391 let s: &str = self.as_ref();
392 s.to_string()
393 }
394}
395
396impl IntoIterator for SymbolSmall {
397 type Item = char;
398 type IntoIter = SymbolSmallIter;
399 fn into_iter(self) -> Self::IntoIter {
400 SymbolSmallIter(self.as_val().get_body())
401 }
402}
403
404#[repr(transparent)]
407#[derive(Clone)]
408pub struct SymbolSmallIter(u64);
409
410impl Iterator for SymbolSmallIter {
411 type Item = char;
412
413 fn next(&mut self) -> Option<Self::Item> {
414 while self.0 != 0 {
415 let res = match ((self.0 >> ((MAX_SMALL_CHARS - 1) * CODE_BITS)) & CODE_MASK) as u8 {
416 1 => b'_',
417 n @ (2..=11) => b'0' + n - 2,
418 n @ (12..=37) => b'A' + n - 12,
419 n @ (38..=63) => b'a' + n - 38,
420 _ => b'\0',
421 };
422 self.0 <<= CODE_BITS;
423 if res != b'\0' {
424 return Some(res as char);
425 }
426 }
427 None
428 }
429}
430
431#[cfg(feature = "testutils")]
432impl FromIterator<char> for SymbolSmall {
433 fn from_iter<T: IntoIterator<Item = char>>(iter: T) -> Self {
434 let mut accum: u64 = 0;
435 for (n, i) in iter.into_iter().enumerate() {
436 if n >= MAX_SMALL_CHARS {
437 panic!("too many chars for SymbolSmall");
438 }
439 accum <<= CODE_BITS;
440 let v = match i {
441 '_' => 1,
442 '0'..='9' => 2 + ((i as u64) - ('0' as u64)),
443 'A'..='Z' => 12 + ((i as u64) - ('A' as u64)),
444 'a'..='z' => 38 + ((i as u64) - ('a' as u64)),
445 _ => break,
446 };
447 accum |= v;
448 }
449 unsafe { Self::from_body(accum) }
450 }
451}
452
453#[cfg(feature = "std")]
454use crate::xdr::{ScSymbol, ScVal};
455
456#[cfg(feature = "std")]
457impl TryFrom<ScVal> for SymbolSmall {
458 type Error = crate::Error;
459 fn try_from(v: ScVal) -> Result<Self, Self::Error> {
460 (&v).try_into()
461 }
462}
463#[cfg(feature = "std")]
464impl TryFrom<&ScVal> for SymbolSmall {
465 type Error = crate::Error;
466 fn try_from(v: &ScVal) -> Result<Self, Self::Error> {
467 if let ScVal::Symbol(ScSymbol(vec)) = v {
468 vec.try_into().map_err(Into::into)
469 } else {
470 Err(ConversionError.into())
471 }
472 }
473}
474
475#[cfg(feature = "std")]
476impl<E: Env> TryFromVal<E, ScVal> for Symbol {
477 type Error = crate::Error;
478
479 fn try_from_val(env: &E, v: &ScVal) -> Result<Self, Self::Error> {
480 Symbol::try_from_val(env, &v)
481 }
482}
483
484#[cfg(feature = "std")]
485impl<E: Env> TryFromVal<E, &ScVal> for Symbol {
486 type Error = crate::Error;
487 fn try_from_val(env: &E, v: &&ScVal) -> Result<Self, Self::Error> {
488 if let ScVal::Symbol(sym) = v {
489 Symbol::try_from_val(env, &sym)
490 } else {
491 Err(ConversionError.into())
492 }
493 }
494}
495
496#[cfg(feature = "std")]
497impl<E: Env> TryFromVal<E, ScSymbol> for Symbol {
498 type Error = crate::Error;
499
500 fn try_from_val(env: &E, v: &ScSymbol) -> Result<Self, Self::Error> {
501 Symbol::try_from_val(env, &v)
502 }
503}
504
505#[cfg(feature = "std")]
506impl<E: Env> TryFromVal<E, &ScSymbol> for Symbol {
507 type Error = crate::Error;
508 fn try_from_val(env: &E, v: &&ScSymbol) -> Result<Self, Self::Error> {
509 Symbol::try_from_val(env, &v.0.as_slice())
510 }
511}
512
513#[cfg(feature = "std")]
514impl TryFrom<SymbolSmall> for ScVal {
515 type Error = crate::Error;
516 fn try_from(s: SymbolSmall) -> Result<Self, crate::Error> {
517 let res: Result<Vec<u8>, _> = s.into_iter().map(<u8 as TryFrom<char>>::try_from).collect();
518 let vec = res.map_err(|_| {
519 crate::Error::from_type_and_code(
520 crate::xdr::ScErrorType::Value,
521 crate::xdr::ScErrorCode::InvalidInput,
522 )
523 })?;
524 Ok(ScVal::Symbol(vec.try_into()?))
525 }
526}
527
528#[cfg(feature = "std")]
529impl TryFrom<&SymbolSmall> for ScVal {
530 type Error = crate::Error;
531 fn try_from(s: &SymbolSmall) -> Result<Self, crate::Error> {
532 (*s).try_into()
533 }
534}
535
536#[cfg(feature = "std")]
537impl<E: Env> TryFromVal<E, Symbol> for ScVal {
538 type Error = crate::Error;
539 fn try_from_val(e: &E, s: &Symbol) -> Result<Self, crate::Error> {
540 Ok(ScVal::Symbol(ScSymbol::try_from_val(e, s)?))
541 }
542}
543
544#[cfg(feature = "std")]
545impl<E: Env> TryFromVal<E, Symbol> for ScSymbol {
546 type Error = crate::Error;
547 fn try_from_val(e: &E, s: &Symbol) -> Result<Self, crate::Error> {
548 let sstr = SymbolStr::try_from_val(e, s)?;
549 Ok(ScSymbol(sstr.0.as_slice()[0..sstr.len()].try_into()?))
550 }
551}
552
553#[cfg(test)]
554mod test_without_string {
555 use super::{SymbolSmall, SymbolStr};
556
557 #[test]
558 fn test_roundtrip() {
559 let input = "stellar";
560 let sym = SymbolSmall::try_from_str(input).unwrap();
561 let sym_str = SymbolStr::from(sym);
562 let s: &str = sym_str.as_ref();
563 assert_eq!(s, input);
564 }
565
566 #[test]
567 fn test_roundtrip_zero() {
568 let input = "";
569 let sym = SymbolSmall::try_from_str(input).unwrap();
570 let sym_str = SymbolStr::from(sym);
571 let s: &str = sym_str.as_ref();
572 assert_eq!(s, input);
573 }
574
575 #[test]
576 fn test_roundtrip_nine() {
577 let input = "123456789";
578 let sym = SymbolSmall::try_from_str(input).unwrap();
579 let sym_str = SymbolStr::from(sym);
580 let s: &str = sym_str.as_ref();
581 assert_eq!(s, input);
582 }
583
584 #[test]
585 fn test_enc() {
586 let vectors: &[(&str, u64)] = &[
588 ("a", 0b__000_000__000_000__000_000__000_000__000_000__000_000__000_000__000_000__100_110_u64),
589 ("ab", 0b__000_000__000_000__000_000__000_000__000_000__000_000__000_000__100_110__100_111_u64),
590 ("abc", 0b__000_000__000_000__000_000__000_000__000_000__000_000__100_110__100_111__101_000_u64),
591 ("ABC", 0b__000_000__000_000__000_000__000_000__000_000__000_000__001_100__001_101__001_110_u64),
592 ("____5678", 0b__000_000__000_001__000_001__000_001__000_001__000_111__001_000__001_001__001_010_u64),
593 ("____56789", 0b__000_001__000_001__000_001__000_001__000_111__001_000__001_001__001_010__001_011_u64),
594 ];
595 for (s, body) in vectors.iter() {
596 let sym = SymbolSmall::try_from_str(s).unwrap();
597 assert_eq!(unsafe { sym.get_body() }, *body);
598 }
599 }
600
601 #[test]
602 fn test_ord() {
603 let vals = ["Hello", "hello", "hellos", "", "_________", "________"];
604 for a in vals.iter() {
605 let a_sym = SymbolSmall::try_from_str(a).unwrap();
606 for b in vals.iter() {
607 let b_sym = SymbolSmall::try_from_str(b).unwrap();
608 assert_eq!(a.cmp(b), a_sym.cmp(&b_sym));
609 }
610 }
611 }
612}
613
614#[cfg(all(test, feature = "std"))]
615mod test_with_string {
616 use super::SymbolSmall;
617 use std::string::{String, ToString};
618
619 #[test]
620 fn test_roundtrip() {
621 let input = "stellar";
622 let sym = SymbolSmall::try_from_str(input).unwrap();
623 let s: String = sym.to_string();
624 assert_eq!(input, &s);
625 }
626
627 #[test]
628 fn test_roundtrip_zero() {
629 let input = "";
630 let sym = SymbolSmall::try_from_str(input).unwrap();
631 let s: String = sym.to_string();
632 assert_eq!(input, &s);
633 }
634
635 #[test]
636 fn test_roundtrip_nine() {
637 let input = "123456789";
638 let sym = SymbolSmall::try_from_str(input).unwrap();
639 let s: String = sym.to_string();
640 assert_eq!(input, &s);
641 }
642}