Skip to main content

rune/runtime/
format.rs

1//! Types for dealing with formatting specifications.
2
3use core::fmt;
4use core::iter;
5use core::mem::take;
6use core::num::NonZeroUsize;
7use core::str;
8
9#[cfg(feature = "musli")]
10use musli_core::{Decode, Encode};
11#[cfg(feature = "serde")]
12use serde::{Deserialize, Serialize};
13
14use crate as rune;
15use crate::alloc::clone::TryClone;
16use crate::alloc::fmt::TryWrite;
17use crate::alloc::{self, String};
18use crate::runtime::{Formatter, Inline, ProtocolCaller, Repr, Value, VmError, VmErrorKind};
19use crate::{Any, TypeHash};
20
21/// Error raised when trying to parse a type string and it fails.
22#[derive(Debug, Clone, Copy)]
23#[non_exhaustive]
24pub struct TypeFromStrError;
25
26impl fmt::Display for TypeFromStrError {
27    #[inline]
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        write!(f, "Bad type string")
30    }
31}
32
33/// Error raised when trying to parse an alignment string and it fails.
34#[derive(Debug, Clone, Copy)]
35pub struct AlignmentFromStrError;
36
37impl fmt::Display for AlignmentFromStrError {
38    #[inline]
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        write!(f, "Bad alignment string")
41    }
42}
43
44/// A format specification, wrapping an inner value.
45#[derive(Any, Debug, Clone, TryClone)]
46#[rune(item = ::std::fmt)]
47pub struct Format {
48    /// The value being formatted.
49    #[rune(dismantle)]
50    pub(crate) value: Value,
51    /// The specification.
52    #[try_clone(copy)]
53    pub(crate) spec: FormatSpec,
54}
55
56/// A format specification.
57#[derive(Debug, Clone, Copy, TryClone)]
58#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
59#[cfg_attr(feature = "musli", derive(Decode, Encode), musli(crate = musli_core))]
60#[try_clone(copy)]
61#[non_exhaustive]
62pub struct FormatSpec {
63    /// Formatting flags.
64    pub(crate) flags: Flags,
65    /// The fill character.
66    pub(crate) fill: char,
67    /// The alignment specification.
68    pub(crate) align: Alignment,
69    /// Formatting width.
70    pub(crate) width: Option<NonZeroUsize>,
71    /// Formatting precision, which may be zero.
72    pub(crate) precision: Option<usize>,
73    /// The type specification.
74    pub(crate) format_type: Type,
75}
76
77/// How a value is padded out to the width it was written with.
78#[derive(Clone, Copy)]
79struct Padding {
80    align: Alignment,
81    fill: char,
82    /// The sign written in front of the digits, which is written apart from
83    /// them so that zero padding can go between the two.
84    sign: Option<char>,
85    /// Whether the padding is what the value is written in rather than what it
86    /// is surrounded by, which is what zero padding is.
87    ///
88    /// It is only ever asked for by a number: the flag which asks for it is
89    /// ignored for everything else, which pads the way it was told to.
90    zero: bool,
91}
92
93impl FormatSpec {
94    /// Construct a new format specification.
95    pub fn new(
96        flags: Flags,
97        fill: char,
98        align: Alignment,
99        width: Option<NonZeroUsize>,
100        precision: Option<usize>,
101        format_type: Type,
102    ) -> Self {
103        Self {
104            flags,
105            fill,
106            align,
107            width,
108            precision,
109            format_type,
110        }
111    }
112
113    /// The padding a value which is not a number is written with.
114    fn padding(&self) -> Padding {
115        Padding {
116            align: self.align,
117            fill: self.fill,
118            sign: None,
119            zero: false,
120        }
121    }
122
123    /// The padding a number which is being zero padded is written with.
124    fn zero_padding(&self, sign: Option<char>) -> Padding {
125        Padding {
126            align: Alignment::Right,
127            fill: '0',
128            sign,
129            zero: true,
130        }
131    }
132
133    /// get traits out of a floating point number.
134    fn float_traits(&self, n: f64) -> (f64, Padding) {
135        if self.flags.test(Flag::SignAwareZeroPad) {
136            if n.is_sign_negative() {
137                (-n, self.zero_padding(Some('-')))
138            } else {
139                (n, self.zero_padding(self.zero_pad_sign()))
140            }
141        } else if self.flags.test(Flag::SignPlus) && n.is_sign_positive() {
142            (
143                n,
144                Padding {
145                    sign: Some('+'),
146                    ..self.padding()
147                },
148            )
149        } else {
150            (n, self.padding())
151        }
152    }
153
154    /// The sign to write in front of a number which is being zero padded.
155    ///
156    /// The padding goes between the sign and the digits, so the sign is written
157    /// separately, and one which was asked for with `+` has to be written here
158    /// as well as the one a negative number carries.
159    fn zero_pad_sign(&self) -> Option<char> {
160        if self.flags.test(Flag::SignPlus) {
161            return Some('+');
162        }
163
164        None
165    }
166
167    /// get traits out of an unsigned integer.
168    fn uint_traits(&self, n: u64) -> (u64, Padding) {
169        if self.flags.test(Flag::SignAwareZeroPad) {
170            (n, self.zero_padding(self.zero_pad_sign()))
171        } else if self.flags.test(Flag::SignPlus) {
172            (
173                n,
174                Padding {
175                    sign: Some('+'),
176                    ..self.padding()
177                },
178            )
179        } else {
180            (n, self.padding())
181        }
182    }
183
184    /// get traits out of an integer.
185    ///
186    /// The sign is written separately from the digits, so the magnitude is
187    /// returned rather than the number itself. It is taken as an unsigned
188    /// number because the magnitude of the smallest signed number is not one.
189    fn int_traits(&self, n: i64) -> (u64, Padding) {
190        let magnitude = n.unsigned_abs();
191
192        if self.flags.test(Flag::SignAwareZeroPad) {
193            if n < 0 {
194                (magnitude, self.zero_padding(Some('-')))
195            } else {
196                (magnitude, self.zero_padding(self.zero_pad_sign()))
197            }
198        } else if n < 0 {
199            (
200                magnitude,
201                Padding {
202                    sign: Some('-'),
203                    ..self.padding()
204                },
205            )
206        } else if self.flags.test(Flag::SignPlus) {
207            (
208                magnitude,
209                Padding {
210                    sign: Some('+'),
211                    ..self.padding()
212                },
213            )
214        } else {
215            (magnitude, self.padding())
216        }
217    }
218
219    /// Format the given unsigned number.
220    fn format_unsigned(&self, buf: &mut String, n: u64) -> alloc::Result<()> {
221        let mut buffer = itoa::Buffer::new();
222        buf.try_push_str(buffer.format(n))?;
223        Ok(())
224    }
225
226    /// Write a string, which the precision cuts short.
227    fn format_str(&self, buf: &mut String, s: &str) -> alloc::Result<()> {
228        let Some(precision) = self.precision else {
229            buf.try_push_str(s)?;
230            return Ok(());
231        };
232
233        for c in s.chars().take(precision) {
234            buf.try_push(c)?;
235        }
236
237        Ok(())
238    }
239
240    /// Format the given float.
241    fn format_float(&self, buf: &mut String, n: f64) -> alloc::Result<()> {
242        if let Some(precision) = self.precision {
243            write!(buf, "{:.*}", precision, n)?;
244        } else {
245            let mut buffer = ryu::Buffer::new();
246            buf.try_push_str(buffer.format(n))?;
247        }
248
249        Ok(())
250    }
251
252    /// Format fill.
253    fn format_fill(&self, f: &mut Formatter, padding: Padding) -> alloc::Result<()> {
254        self.format_fill_with(f, padding, "")
255    }
256
257    /// Format fill, writing `prefix` between the sign and the digits.
258    ///
259    /// A sign and a radix prefix belong to the digits rather than to the
260    /// padding, so they are written next to them and count towards the width
261    /// the same way. Zero padding is the exception: it is what a number is
262    /// written in rather than what it is surrounded by, so it goes between the
263    /// sign and the digits.
264    fn format_fill_with(
265        &self,
266        f: &mut Formatter,
267        padding: Padding,
268        prefix: &str,
269    ) -> alloc::Result<()> {
270        let Padding {
271            align,
272            fill,
273            sign,
274            zero,
275        } = padding;
276
277        let (f, buf) = f.parts_mut();
278
279        let mut w = self.width.map(|n| n.get()).unwrap_or_default();
280
281        w = w
282            .saturating_sub(buf.chars().count())
283            .saturating_sub(prefix.chars().count())
284            .saturating_sub(sign.map(|_| 1).unwrap_or_default());
285
286        let head = |f: &mut dyn TryWrite| {
287            if let Some(sign) = sign {
288                f.try_write_char(sign)?;
289            }
290
291            f.try_write_str(prefix)
292        };
293
294        if w == 0 {
295            head(f)?;
296            f.try_write_str(buf)?;
297            return Ok(());
298        }
299
300        if zero {
301            head(f)?;
302
303            for c in iter::repeat_n(fill, w) {
304                f.try_write_char(c)?;
305            }
306
307            f.try_write_str(buf)?;
308            return Ok(());
309        }
310
311        let mut filler = iter::repeat_n(fill, w);
312
313        match align {
314            Alignment::Left => {
315                head(f)?;
316                f.try_write_str(buf)?;
317
318                for c in filler {
319                    f.try_write_char(c)?;
320                }
321            }
322            Alignment::Center => {
323                for c in (&mut filler).take(w / 2) {
324                    f.try_write_char(c)?;
325                }
326
327                head(f)?;
328                f.try_write_str(buf)?;
329
330                for c in filler {
331                    f.try_write_char(c)?;
332                }
333            }
334            Alignment::Right => {
335                for c in filler {
336                    f.try_write_char(c)?;
337                }
338
339                head(f)?;
340                f.try_write_str(buf)?;
341            }
342        }
343
344        Ok(())
345    }
346
347    fn format_display(
348        &self,
349        value: &Value,
350        f: &mut Formatter,
351        caller: &mut dyn ProtocolCaller,
352    ) -> Result<(), VmError> {
353        'fallback: {
354            match value.as_ref() {
355                Repr::Inline(value) => match value {
356                    Inline::Char(c) => {
357                        f.buf_mut().try_push(*c)?;
358                        self.format_fill(f, self.padding())?;
359                    }
360                    Inline::Signed(n) => {
361                        let (n, padding) = self.int_traits(*n);
362                        self.format_unsigned(f.buf_mut(), n)?;
363                        self.format_fill(f, padding)?;
364                    }
365                    Inline::Unsigned(n) => {
366                        let (n, padding) = self.uint_traits(*n);
367                        self.format_unsigned(f.buf_mut(), n)?;
368                        self.format_fill(f, padding)?;
369                    }
370                    Inline::Bool(b) => {
371                        f.buf_mut()
372                            .try_push_str(if *b { "true" } else { "false" })?;
373                        self.format_fill(f, self.padding())?;
374                    }
375                    Inline::Float(n) => {
376                        let (n, padding) = self.float_traits(*n);
377                        self.format_float(f.buf_mut(), n)?;
378                        self.format_fill(f, padding)?;
379                    }
380                    _ => {
381                        break 'fallback;
382                    }
383                },
384                Repr::Dynamic(..) => {
385                    break 'fallback;
386                }
387                Repr::Any(value) => match value.type_hash() {
388                    String::HASH => {
389                        let s = value.borrow_ref::<String>()?;
390                        self.format_str(f.buf_mut(), &s)?;
391                        self.format_fill(f, self.padding())?;
392                    }
393                    _ => {
394                        break 'fallback;
395                    }
396                },
397            }
398
399            return Ok(());
400        }
401
402        value.display_fmt_with(f, caller)
403    }
404
405    fn format_debug(
406        &self,
407        value: &Value,
408        f: &mut Formatter,
409        caller: &mut dyn ProtocolCaller,
410    ) -> Result<(), VmError> {
411        'fallback: {
412            match value.as_ref() {
413                Repr::Inline(value) => match value {
414                    Inline::Signed(n) => {
415                        let (n, padding) = self.int_traits(*n);
416                        self.format_unsigned(f.buf_mut(), n)?;
417                        self.format_fill(f, padding)?;
418                    }
419                    Inline::Unsigned(n) => {
420                        let (n, padding) = self.uint_traits(*n);
421                        self.format_unsigned(f.buf_mut(), n)?;
422                        self.format_fill(f, padding)?;
423                    }
424                    Inline::Bool(b) => {
425                        f.buf_mut()
426                            .try_push_str(if *b { "true" } else { "false" })?;
427                        self.format_fill(f, self.padding())?;
428                    }
429                    Inline::Float(n) => {
430                        let (n, padding) = self.float_traits(*n);
431                        self.format_float(f.buf_mut(), n)?;
432                        self.format_fill(f, padding)?;
433                    }
434                    _ => {
435                        break 'fallback;
436                    }
437                },
438                Repr::Dynamic(..) => {
439                    break 'fallback;
440                }
441                Repr::Any(value) => match value.type_hash() {
442                    String::HASH => {
443                        let s = value.borrow_ref::<String>()?;
444                        write!(f, "{s:?}")?;
445                    }
446                    _ => {
447                        break 'fallback;
448                    }
449                },
450            }
451
452            return Ok(());
453        };
454
455        value.debug_fmt_with(f, caller)
456    }
457
458    /// The prefix a number written in a radix carries when one was asked for
459    /// with `#`.
460    fn radix_prefix(&self, prefix: &'static str) -> &'static str {
461        if self.flags.test(Flag::Alternate) {
462            prefix
463        } else {
464            ""
465        }
466    }
467
468    fn format_upper_hex(&self, value: &Value, f: &mut Formatter) -> Result<(), VmError> {
469        match value.as_inline() {
470            Some(Inline::Signed(n)) => {
471                let (n, padding) = self.uint_traits(*n as u64);
472                write!(f.buf_mut(), "{n:X}")?;
473                self.format_fill_with(f, padding, self.radix_prefix("0x"))?;
474            }
475            Some(Inline::Unsigned(n)) => {
476                let (n, padding) = self.uint_traits(*n);
477                write!(f.buf_mut(), "{n:X}")?;
478                self.format_fill_with(f, padding, self.radix_prefix("0x"))?;
479            }
480            _ => {
481                return Err(VmError::new(VmErrorKind::IllegalFormat));
482            }
483        }
484
485        Ok(())
486    }
487
488    fn format_lower_hex(&self, value: &Value, f: &mut Formatter) -> Result<(), VmError> {
489        match value.as_inline() {
490            Some(Inline::Signed(n)) => {
491                let (n, padding) = self.uint_traits(*n as u64);
492                write!(f.buf_mut(), "{n:x}")?;
493                self.format_fill_with(f, padding, self.radix_prefix("0x"))?;
494            }
495            Some(Inline::Unsigned(n)) => {
496                let (n, padding) = self.uint_traits(*n);
497                write!(f.buf_mut(), "{n:x}")?;
498                self.format_fill_with(f, padding, self.radix_prefix("0x"))?;
499            }
500            _ => {
501                return Err(VmError::new(VmErrorKind::IllegalFormat));
502            }
503        }
504
505        Ok(())
506    }
507
508    fn format_binary(&self, value: &Value, f: &mut Formatter) -> Result<(), VmError> {
509        match value.as_inline() {
510            Some(Inline::Signed(n)) => {
511                let (n, padding) = self.uint_traits(*n as u64);
512                write!(f.buf_mut(), "{n:b}")?;
513                self.format_fill_with(f, padding, self.radix_prefix("0b"))?;
514            }
515            Some(Inline::Unsigned(n)) => {
516                let (n, padding) = self.uint_traits(*n);
517                write!(f.buf_mut(), "{n:b}")?;
518                self.format_fill_with(f, padding, self.radix_prefix("0b"))?;
519            }
520            _ => {
521                return Err(VmError::new(VmErrorKind::IllegalFormat));
522            }
523        }
524
525        Ok(())
526    }
527
528    fn format_pointer(&self, value: &Value, f: &mut Formatter) -> Result<(), VmError> {
529        match value.as_inline() {
530            Some(Inline::Signed(n)) => {
531                let (n, padding) = self.uint_traits(*n as u64);
532                write!(f.buf_mut(), "{:p}", n as *const ())?;
533                self.format_fill(f, padding)?;
534            }
535            _ => {
536                return Err(VmError::new(VmErrorKind::IllegalFormat));
537            }
538        }
539
540        Ok(())
541    }
542
543    /// Format the given value to the out buffer `out`, using `buf` for
544    /// intermediate work if necessary.
545    pub(crate) fn format(
546        &self,
547        value: &Value,
548        f: &mut Formatter,
549        caller: &mut dyn ProtocolCaller,
550    ) -> Result<(), VmError> {
551        f.buf_mut().clear();
552
553        match self.format_type {
554            Type::Display => self.format_display(value, f, caller)?,
555            Type::Debug => self.format_debug(value, f, caller)?,
556            Type::UpperHex => self.format_upper_hex(value, f)?,
557            Type::LowerHex => self.format_lower_hex(value, f)?,
558            Type::Binary => self.format_binary(value, f)?,
559            Type::Pointer => self.format_pointer(value, f)?,
560        }
561
562        Ok(())
563    }
564}
565
566impl fmt::Display for FormatSpec {
567    #[inline]
568    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
569        write!(
570            f,
571            "format(fill = {fill:?}, align = {align}, flags = {flags:?}, width = {width}, precision = {precision}, format_type = {format_type})",
572            fill = self.fill,
573            align = self.align,
574            flags = self.flags,
575            width = OptionDebug(self.width.as_ref()),
576            precision = OptionDebug(self.precision.as_ref()),
577            format_type = self.format_type
578        )
579    }
580}
581
582struct OptionDebug<'a, T>(Option<&'a T>);
583
584impl<T> fmt::Display for OptionDebug<'_, T>
585where
586    T: fmt::Display,
587{
588    #[inline]
589    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
590        match self.0 {
591            Some(value) => write!(f, "{value}"),
592            None => write!(f, "?"),
593        }
594    }
595}
596
597/// The type of formatting requested.
598#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
599#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
600#[cfg_attr(feature = "musli", derive(Decode, Encode), musli(crate = musli_core))]
601#[non_exhaustive]
602pub enum Type {
603    /// Display type (default).
604    #[default]
605    Display,
606    /// Debug type.
607    Debug,
608    /// Upper hex type.
609    UpperHex,
610    /// Upper hex type.
611    LowerHex,
612    /// Binary formatting type.
613    Binary,
614    /// Pointer formatting type.
615    Pointer,
616}
617
618impl str::FromStr for Type {
619    type Err = TypeFromStrError;
620
621    #[inline]
622    fn from_str(s: &str) -> Result<Self, Self::Err> {
623        match s {
624            "display" => Ok(Self::Display),
625            "debug" => Ok(Self::Debug),
626            "upper_hex" => Ok(Self::UpperHex),
627            "lower_hex" => Ok(Self::LowerHex),
628            "binary" => Ok(Self::Binary),
629            "pointer" => Ok(Self::Pointer),
630            _ => Err(TypeFromStrError),
631        }
632    }
633}
634
635impl fmt::Display for Type {
636    #[inline]
637    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
638        match self {
639            Self::Display => {
640                write!(f, "display")?;
641            }
642            Self::Debug => {
643                write!(f, "debug")?;
644            }
645            Self::UpperHex => {
646                write!(f, "upper_hex")?;
647            }
648            Self::LowerHex => {
649                write!(f, "lower_hex")?;
650            }
651            Self::Binary => {
652                write!(f, "binary")?;
653            }
654            Self::Pointer => {
655                write!(f, "pointer")?;
656            }
657        }
658
659        Ok(())
660    }
661}
662
663/// The alignment requested.
664#[derive(Default, Debug, Clone, Copy, TryClone, PartialEq, Eq)]
665#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
666#[cfg_attr(feature = "musli", derive(Decode, Encode), musli(crate = musli_core))]
667#[try_clone(copy)]
668#[non_exhaustive]
669pub enum Alignment {
670    /// Left alignment.
671    #[default]
672    Left,
673    /// Center alignment.
674    Center,
675    /// Right alignment.
676    Right,
677}
678
679impl str::FromStr for Alignment {
680    type Err = AlignmentFromStrError;
681
682    #[inline]
683    fn from_str(s: &str) -> Result<Self, Self::Err> {
684        match s {
685            "left" => Ok(Self::Left),
686            "center" => Ok(Self::Center),
687            "right" => Ok(Self::Right),
688            _ => Err(AlignmentFromStrError),
689        }
690    }
691}
692
693impl fmt::Display for Alignment {
694    #[inline]
695    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
696        match self {
697            Self::Left => {
698                write!(f, "left")?;
699            }
700            Self::Center => {
701                write!(f, "center")?;
702            }
703            Self::Right => {
704                write!(f, "right")?;
705            }
706        }
707
708        Ok(())
709    }
710}
711
712/// A single flag for format spec.
713#[derive(Clone, Copy)]
714#[repr(u32)]
715#[non_exhaustive]
716pub enum Flag {
717    /// Plus sign `+`.
718    SignPlus,
719    /// Minus sign `-`.
720    SignMinus,
721    /// Alternate specifier `#`.
722    Alternate,
723    /// Sign-aware zero pad `0`.
724    SignAwareZeroPad,
725}
726
727/// Format specification flags.
728#[derive(Clone, Copy, TryClone, Default, PartialEq, Eq)]
729#[repr(transparent)]
730#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(transparent))]
731#[cfg_attr(feature = "musli", derive(Decode, Encode), musli(crate = musli_core, transparent))]
732#[try_clone(copy)]
733pub struct Flags(u32);
734
735impl Flags {
736    /// Check if the set of flags is empty.
737    #[inline]
738    pub fn is_empty(self) -> bool {
739        self.0 == 0
740    }
741
742    /// Get the flags as a number. This representation is not guaranteed to be
743    /// stable.
744    #[inline]
745    pub fn into_u32(self) -> u32 {
746        self.0
747    }
748
749    /// Set the given flag.
750    #[inline]
751    pub fn set(&mut self, flag: Flag) {
752        self.0 |= &(1 << flag as u32);
753    }
754
755    /// Test the given flag.
756    #[inline]
757    pub fn test(&self, flag: Flag) -> bool {
758        (self.0 & (1 << flag as u32)) != 0
759    }
760}
761
762impl From<u32> for Flags {
763    fn from(flags: u32) -> Self {
764        Self(flags)
765    }
766}
767
768impl fmt::Debug for Flags {
769    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
770        macro_rules! fmt_flag {
771            ($flag:ident, $o:ident, $spec:literal) => {
772                if self.test(Flag::$flag) {
773                    if !take(&mut $o) {
774                        write!(f, ", ")?;
775                    }
776
777                    write!(f, $spec)?;
778                }
779            };
780        }
781
782        let mut o = true;
783        write!(f, "Flags{{")?;
784        fmt_flag!(SignPlus, o, "+");
785        fmt_flag!(SignMinus, o, "-");
786        fmt_flag!(Alternate, o, "#");
787        fmt_flag!(SignAwareZeroPad, o, "0");
788        write!(f, "}}")?;
789        Ok(())
790    }
791}