typeline_core/record_data/
formattable.rs

1use std::{
2    borrow::Borrow,
3    io::ErrorKind,
4    ops::{DivAssign, Rem, SubAssign},
5};
6
7use metamatch::metamatch;
8use num::{
9    BigInt, BigRational, FromPrimitive, One, Signed, ToPrimitive, Zero,
10};
11
12use crate::{
13    cli::call_expr::Argument,
14    operators::errors::OperatorApplicationError,
15    options::chain_settings::RationalsPrintMode,
16    record_data::{
17        array::Array,
18        field_value::{Object, Undefined},
19        scope_manager::OpDeclRef,
20        stream_value::StreamValueData,
21    },
22    utils::{
23        counting_writer::{
24            CharLimitedLengthAndCharsCountingWriter,
25            LengthAndCharsCountingWriter, LengthCountingWriter,
26        },
27        escaped_writer::EscapedWriter,
28        int_string_conversions::i64_digits,
29        lazy_lock_guard::LazyRwLockGuard,
30        string_store::StringStore,
31        text_write::{MaybeTextWrite, TextWrite, TextWriteIoAdapter},
32        MAX_UTF8_CHAR_LEN,
33    },
34    NULL_STR, UNDEFINED_STR,
35};
36use std::ops::{AddAssign, MulAssign, Sub};
37
38use super::{
39    custom_data::CustomData,
40    field::FieldManager,
41    field_value::Null,
42    field_value_ref::FieldValueRef,
43    match_set::MatchSetManager,
44    stream_value::{StreamValue, StreamValueDataType},
45};
46
47// format string grammar:
48#[rustfmt::skip]
49/*
50format_string = ( [<brace_escaped_text>] [format_key] )*
51format_key = '{' [key] [ ':' format_spec ] '}'
52format_spec = [[fill]align]['+']['#'|'##']['0'][width]['.'precision][debug_repr[number_format]]
53fill = <any character>
54align = '<' | '^' | '>'
55debug_repr = ['?' | '??'] ['%'] | '%'
56number_format = 'x' | 'X' | '0x' | '0X' | 'o' | '0o' | 'b' | '0b' | 'e' | 'E'
57key = identifier
58width = identifier | number
59precision = identifier | number
60identifier = basic_identifier | escaped_identifier
61basic_identifier = '\p{XID_Start}' '\p{XID_Continue}'
62escaped_identifier = '@{' <brace_escaped_text> '}'
63*/
64
65#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
66pub enum NumberFormat {
67    #[default]
68    Default, // the default value representation, e.g. '42' for 42
69    Binary,        // base 2, e.g '101010' for 42
70    BinaryZeroB,   // binary, but with leading 0b, e.g. '0b101010' for 42
71    Octal,         // base 8, e.g '52' for 42
72    OctalZeroO,    // ocatl, but with leading 0o, e.g. '0o52' for 42
73    LowerHex,      // lower case hexadecimal, e.g '2a' for 42
74    UpperHex,      // upper case hexadecimal, e.g '2A' for 42
75    LowerHexZeroX, // like LowerHex, but with leading 0x, e.g. '0x2a' for 42
76    UpperHexZeroX, // like UpperHex, but with leading 0x, e.g. '0x2A' for 42
77    LowerExp,      // lower case scientific notation, e.g. '4.2e1' for 42
78    UpperExp,      // upper case scientific notation, e.g. '4.2E1' for 42
79}
80
81#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
82pub enum TypeReprFormat {
83    #[default]
84    Regular, // regular representation, without string escaping etc.
85    Typed, // typed + escaped, e.g. "foo\n" insead of foo<newline>, no errors
86    Debug, // like typed, but prefix ~ for stream values
87}
88
89#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
90pub enum PrettyPrintFormat {
91    #[default]
92    Regular, // default. objects & arrays get spaces, but stay on one line
93    Pretty,  /* add newlines and indentation to objects. adds 0x to hex
94              * numbers */
95    Compact, // no spaces at all
96}
97
98#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
99pub struct FormatFillSpec {
100    pub fill_char: Option<char>,
101    pub alignment: FormatFillAlignment,
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
105pub enum FormatFillAlignment {
106    #[default]
107    Right,
108    Left,
109    Center,
110}
111
112impl FormatFillSpec {
113    pub fn new(
114        fill_char: Option<char>,
115        alignment: FormatFillAlignment,
116    ) -> Self {
117        Self {
118            fill_char,
119            alignment,
120        }
121    }
122    pub fn get_fill_char(&self) -> char {
123        self.fill_char.unwrap_or(' ')
124    }
125    pub fn distribute_padding(&self, padding: usize) -> (usize, usize) {
126        match self.alignment {
127            FormatFillAlignment::Left => (padding, 0),
128            FormatFillAlignment::Center => ((padding + 1) / 2, padding / 2),
129            FormatFillAlignment::Right => (0, padding),
130        }
131    }
132}
133
134#[derive(Clone, Debug, PartialEq, Eq, Default)]
135pub struct FormatOptions {
136    pub fill: Option<FormatFillSpec>,
137    pub add_plus_sign: bool,
138
139    pub number_format: NumberFormat,
140
141    pub type_repr: TypeReprFormat,
142
143    pub pretty_print: PrettyPrintFormat,
144
145    pub zero_pad_numbers: bool,
146}
147
148#[derive(Clone, Debug, PartialEq, Eq, Default)]
149pub struct RealizedFormatKey {
150    pub min_char_count: usize,
151    pub float_precision: Option<usize>,
152    pub opts: FormatOptions,
153}
154
155pub struct TextLayout {
156    pub truncated_text_len: usize,
157    pub padding: usize,
158}
159
160pub struct TextBounds {
161    pub len: usize,
162    pub char_count: usize,
163}
164
165#[derive(Clone, Copy)]
166pub struct ValueFormattingOpts {
167    pub is_stream_value: bool,
168    pub type_repr_format: TypeReprFormat,
169}
170
171#[derive(Default)]
172pub struct FormattingContext<'a, 'b> {
173    pub ss: Option<&'a mut LazyRwLockGuard<'b, StringStore>>,
174    pub fm: Option<&'a FieldManager>,
175    pub msm: Option<&'a MatchSetManager>,
176    pub rationals_print_mode: RationalsPrintMode,
177    pub is_stream_value: bool,
178    pub rfk: RealizedFormatKey,
179}
180
181impl ValueFormattingOpts {
182    pub fn for_nested_value() -> Self {
183        ValueFormattingOpts {
184            is_stream_value: false,
185            type_repr_format: TypeReprFormat::Typed,
186        }
187    }
188}
189
190impl TypeReprFormat {
191    pub fn is_typed(&self) -> bool {
192        match self {
193            TypeReprFormat::Regular => false,
194            TypeReprFormat::Typed | TypeReprFormat::Debug => true,
195        }
196    }
197}
198
199impl TextBounds {
200    fn new(len: usize, char_count: usize) -> Self {
201        Self { len, char_count }
202    }
203}
204
205impl TextLayout {
206    pub fn new(truncated_text_len: usize, padding: usize) -> Self {
207        Self {
208            truncated_text_len,
209            padding,
210        }
211    }
212    pub fn total_len(&self, opts: &FormatOptions) -> usize {
213        self.truncated_text_len + self.padding * opts.fill_char_width()
214    }
215}
216
217impl FormatOptions {
218    fn fill_char_width(&self) -> usize {
219        if let Some(f) = self.fill {
220            return f.fill_char.map_or(1, char::len_utf8);
221        }
222        1
223    }
224}
225
226impl RealizedFormatKey {
227    pub fn with_type_repr(repr: TypeReprFormat) -> Self {
228        Self {
229            opts: FormatOptions {
230                type_repr: repr,
231                ..FormatOptions::default()
232            },
233            ..Self::default()
234        }
235    }
236    pub fn must_buffer_stream(&self, sv: &StreamValue) -> bool {
237        match self.opts.type_repr {
238            TypeReprFormat::Regular => {}
239            TypeReprFormat::Typed | TypeReprFormat::Debug => {
240                if sv.data_type.is_none()
241                    || sv.data_type == Some(StreamValueDataType::MaybeText)
242                {
243                    return true;
244                }
245            }
246        }
247        if self.min_char_count > 0
248            && sv.data_len_present() < self.min_char_count
249        {
250            return true;
251        }
252        false
253    }
254}
255
256pub trait Formattable<'a, 'b> {
257    type Context;
258    fn format<W: MaybeTextWrite + ?Sized>(
259        &self,
260        ctx: &mut Self::Context,
261        w: &mut W,
262    ) -> std::io::Result<()>;
263    fn refuses_truncation(&self, _ctx: &mut Self::Context) -> bool {
264        true
265    }
266    fn total_length_cheap(&self, _ctx: &mut Self::Context) -> bool {
267        false
268    }
269    fn length_total(&self, ctx: &mut Self::Context) -> usize {
270        let mut w = LengthCountingWriter::default();
271        self.format(ctx, &mut w).unwrap();
272        w.len
273    }
274    fn text_bounds_total(&self, ctx: &mut Self::Context) -> TextBounds {
275        let mut w = LengthAndCharsCountingWriter::default();
276        self.format(ctx, &mut w).unwrap();
277        TextBounds::new(w.len, w.char_count)
278    }
279    fn char_bound_text_bounds(
280        &self,
281        ctx: &mut Self::Context,
282        max_chars: usize,
283    ) -> TextBounds {
284        if self.refuses_truncation(ctx) {
285            return self.text_bounds_total(ctx);
286        }
287        let mut w = CharLimitedLengthAndCharsCountingWriter::new(max_chars);
288        if let Err(e) = self.format(ctx, &mut w) {
289            assert!(std::io::ErrorKind::WriteZero == e.kind());
290        }
291        TextBounds::new(w.len, w.char_count)
292    }
293}
294
295impl Formattable<'_, '_> for [u8] {
296    type Context = ValueFormattingOpts;
297    fn refuses_truncation(&self, opts: &mut Self::Context) -> bool {
298        opts.type_repr_format == TypeReprFormat::Regular
299    }
300    fn total_length_cheap(&self, opts: &mut Self::Context) -> bool {
301        opts.type_repr_format != TypeReprFormat::Regular
302    }
303    fn format<W: MaybeTextWrite + ?Sized>(
304        &self,
305        opts: &mut Self::Context,
306        w: &mut W,
307    ) -> std::io::Result<()> {
308        if opts.type_repr_format == TypeReprFormat::Regular {
309            return w.write_all(self);
310        }
311        if opts.is_stream_value
312            && opts.type_repr_format == TypeReprFormat::Debug
313        {
314            w.write_all_text("~b\"")?;
315        } else {
316            w.write_all_text("b\"")?;
317        }
318        let mut ew = EscapedWriter::new(w, b'"');
319        std::io::Write::write_all(&mut ew, self)?;
320        ew.into_inner()?.write_all_text("\"")
321    }
322    fn length_total(&self, opts: &mut Self::Context) -> usize {
323        if opts.type_repr_format == TypeReprFormat::Regular {
324            return self.len();
325        }
326        let mut w = LengthCountingWriter::default();
327        self.format(opts, &mut w).unwrap();
328        w.len
329    }
330}
331impl Formattable<'_, '_> for str {
332    type Context = ValueFormattingOpts;
333    fn refuses_truncation(&self, opts: &mut Self::Context) -> bool {
334        opts.type_repr_format == TypeReprFormat::Regular
335    }
336    fn total_length_cheap(&self, opts: &mut Self::Context) -> bool {
337        opts.type_repr_format != TypeReprFormat::Regular
338    }
339    fn format<W: MaybeTextWrite + ?Sized>(
340        &self,
341        opts: &mut Self::Context,
342        w: &mut W,
343    ) -> std::io::Result<()> {
344        if opts.type_repr_format == TypeReprFormat::Regular {
345            return w.write_all_text(self);
346        }
347        if opts.is_stream_value
348            && opts.type_repr_format == TypeReprFormat::Debug
349        {
350            w.write_all_text("~\"")?;
351        } else {
352            w.write_all_text("\"")?;
353        }
354        let mut ew = EscapedWriter::new(w, b'"');
355        TextWrite::write_all_text(&mut ew, self)?;
356        ew.into_inner()?.write_all_text("\"")
357    }
358    fn length_total(&self, opts: &mut Self::Context) -> usize {
359        if opts.type_repr_format == TypeReprFormat::Regular {
360            return self.len();
361        }
362        let mut w = LengthCountingWriter::default();
363        self.format(opts, &mut w).unwrap();
364        w.len
365    }
366}
367impl Formattable<'_, '_> for i64 {
368    type Context = RealizedFormatKey;
369    fn total_length_cheap(&self, _ctx: &mut Self::Context) -> bool {
370        true
371    }
372    fn format<W: MaybeTextWrite + ?Sized>(
373        &self,
374        ctx: &mut Self::Context,
375        w: &mut W,
376    ) -> std::io::Result<()> {
377        let char_count = ctx.min_char_count;
378        if ctx.opts.add_plus_sign {
379            if ctx.opts.zero_pad_numbers {
380                return w.write_text_fmt(format_args!("{self:+0char_count$}"));
381            }
382            return w.write_text_fmt(format_args!("{self:+char_count$}"));
383        }
384        if ctx.opts.zero_pad_numbers {
385            return w.write_text_fmt(format_args!("{self:0char_count$}"));
386        }
387        w.write_text_fmt(format_args!("{self}"))
388    }
389    fn length_total(&self, ctx: &mut Self::Context) -> usize {
390        let digits = i64_digits(ctx.opts.add_plus_sign, *self);
391        if !ctx.opts.zero_pad_numbers {
392            return digits;
393        }
394        digits.max(ctx.min_char_count)
395    }
396    fn text_bounds_total(&self, ctx: &mut Self::Context) -> TextBounds {
397        let len = self.length_total(ctx);
398        TextBounds::new(len, len)
399    }
400}
401impl<'a, 'b: 'a> Formattable<'a, 'b> for Object {
402    type Context = FormattingContext<'a, 'b>;
403
404    fn format<W: MaybeTextWrite + ?Sized>(
405        &self,
406        fc: &mut Self::Context,
407        w: &mut W,
408    ) -> std::io::Result<()> {
409        w.write_all_text("{")?;
410        // TODO: escape keys
411        let mut first = true;
412        match self {
413            Object::KeysStored(m) => {
414                for (k, v) in m {
415                    if first {
416                        first = false;
417                    } else {
418                        w.write_all_text(", ")?;
419                    }
420                    format_quoted_string_raw(w, k)?;
421                    w.write_all_text(": ")?;
422                    Formattable::format(&v.as_ref(), fc, w)?;
423                }
424            }
425            Object::KeysInterned(m) => {
426                for (&k, v) in m {
427                    if first {
428                        first = false;
429                    } else {
430                        w.write_all_text(", ")?;
431                    }
432                    let Some(ss) = &mut fc.ss else {
433                        return Err(std::io::Error::new(
434                            ErrorKind::InvalidInput,
435                            "string store needed but not supplied",
436                        ));
437                    };
438                    format_quoted_string_raw(w, ss.get().lookup(k))?;
439                    w.write_all_text(": ")?;
440                    Formattable::format(&v.as_ref(), fc, w)?;
441                }
442            }
443        }
444        w.write_all_text("}")
445    }
446}
447impl<'a, 'b: 'a> Formattable<'a, 'b> for Array {
448    type Context = FormattingContext<'a, 'b>;
449
450    fn format<W: MaybeTextWrite + ?Sized>(
451        &self,
452        fc: &mut Self::Context,
453        w: &mut W,
454    ) -> std::io::Result<()> {
455        fn format_array<
456            'a,
457            'b,
458            T: Formattable<'a, 'b> + ?Sized,
459            I: IntoIterator<Item = impl Borrow<T>>,
460            W: MaybeTextWrite + ?Sized,
461        >(
462            iter: I,
463            fc: &mut T::Context,
464            w: &mut W,
465        ) -> std::io::Result<()> {
466            w.write_all_text("[")?;
467            let mut first = true;
468            for x in iter {
469                if first {
470                    first = false;
471                } else {
472                    w.write_all_text(", ")?;
473                }
474                x.borrow().format(fc, w)?;
475            }
476            w.write_all_text("]")?;
477            Ok(())
478        }
479        let repr_before = fc.rfk.opts.type_repr;
480        if repr_before != TypeReprFormat::Debug {
481            fc.rfk.opts.type_repr = TypeReprFormat::Typed;
482        }
483        let res = metamatch!(match self {
484            #[expand(REP in [Null, Undefined])]
485            Array::REP(count) => {
486                format_array(std::iter::repeat(REP).take(*count), &mut (), w)
487            }
488
489            #[expand((REP, T, FC) in [
490                (Int, i64, &mut fc.rfk),
491                (Float, f64, &mut fc.rfk),
492                (Array, Array, fc),
493                (Object, Object, fc),
494                (BigInt, BigInt, &mut fc.rfk),
495                (BigRational, BigRational, fc),
496                (Argument, Argument, fc),
497                (OpDecl, OpDeclRef, fc),
498                (Error, OperatorApplicationError, &mut fc.value_formatting_opts()),
499            ])]
500            Array::REP(v) => {
501                format_array::<T, _, _>(&**v, FC, w)
502            }
503
504            #[expand((REP, T, FC) in [
505                (Text, str, &mut fc.value_formatting_opts()),
506                (Bytes, [u8], &mut fc.value_formatting_opts()),
507                (Custom, dyn CustomData, &mut fc.rfk)
508            ])]
509            Array::REP(v) => {
510                format_array::<T, _, _>(v.iter().map(|v| &**v), FC, w)
511            }
512
513            Array::FieldReference(_) | Array::SlicedFieldReference(_) => {
514                todo!()
515            }
516
517            Array::Mixed(v) => fc.for_nested_values(|fc| {
518                format_array(v.iter().map(|v| v.as_ref()), fc, w)
519            }),
520
521            Array::StreamValueId(_) => todo!(),
522        });
523        fc.rfk.opts.type_repr = repr_before;
524        res
525    }
526}
527impl<'a, 'b: 'a> Formattable<'a, 'b> for BigRational {
528    type Context = FormattingContext<'a, 'b>;
529    fn format<W: MaybeTextWrite + ?Sized>(
530        &self,
531        fc: &mut Self::Context,
532        w: &mut W,
533    ) -> std::io::Result<()> {
534        // TODO: we dont support zero pad etc. for now
535        format_rational(w, self, fc.rationals_print_mode)
536    }
537}
538impl Formattable<'_, '_> for BigInt {
539    type Context = RealizedFormatKey;
540    fn format<W: MaybeTextWrite + ?Sized>(
541        &self,
542        _fc: &mut Self::Context,
543        w: &mut W,
544    ) -> std::io::Result<()> {
545        // TODO: we dont support zero pad etc. for now
546        w.write_text_fmt(format_args!("{self}"))
547    }
548}
549impl Formattable<'_, '_> for f64 {
550    type Context = RealizedFormatKey;
551    fn format<W: MaybeTextWrite + ?Sized>(
552        &self,
553        ctx: &mut Self::Context,
554        w: &mut W,
555    ) -> std::io::Result<()> {
556        let char_count = ctx.min_char_count;
557        if let Some(float_prec) = ctx.float_precision {
558            if ctx.opts.add_plus_sign {
559                if ctx.opts.zero_pad_numbers {
560                    return w.write_text_fmt(format_args!(
561                        "{self:+0char_count$.float_prec$}"
562                    ));
563                }
564                return w.write_text_fmt(format_args!(
565                    "{self:+char_count$.float_prec$}"
566                ));
567            }
568            if ctx.opts.zero_pad_numbers {
569                return w.write_text_fmt(format_args!(
570                    "{self:0char_count$.float_prec$}"
571                ));
572            }
573            return w.write_text_fmt(format_args!("{self:.float_prec$}"));
574        }
575        if ctx.opts.add_plus_sign {
576            if ctx.opts.zero_pad_numbers {
577                return w.write_text_fmt(format_args!("{self:+0char_count$}"));
578            }
579            return w.write_text_fmt(format_args!("{self:+char_count$}"));
580        }
581        if ctx.opts.zero_pad_numbers {
582            return w.write_text_fmt(format_args!("{self:0char_count$}"));
583        }
584        w.write_text_fmt(format_args!("{self}"))
585    }
586}
587impl Formattable<'_, '_> for Null {
588    type Context = ();
589    fn total_length_cheap(&self, _ctx: &mut Self::Context) -> bool {
590        true
591    }
592    fn format<W: MaybeTextWrite + ?Sized>(
593        &self,
594        _ctx: &mut Self::Context,
595        w: &mut W,
596    ) -> std::io::Result<()> {
597        w.write_all_text(NULL_STR)
598    }
599    fn length_total(&self, _ctx: &mut Self::Context) -> usize {
600        NULL_STR.len()
601    }
602    fn text_bounds_total(&self, _ctx: &mut Self::Context) -> TextBounds {
603        let len = NULL_STR.len();
604        TextBounds::new(len, len)
605    }
606}
607impl Formattable<'_, '_> for Undefined {
608    type Context = ();
609    fn total_length_cheap(&self, _ctx: &mut Self::Context) -> bool {
610        true
611    }
612    fn format<W: MaybeTextWrite + ?Sized>(
613        &self,
614        _ctx: &mut Self::Context,
615        w: &mut W,
616    ) -> std::io::Result<()> {
617        w.write_all_text(UNDEFINED_STR)
618    }
619    fn length_total(&self, _ctx: &mut Self::Context) -> usize {
620        UNDEFINED_STR.len()
621    }
622    fn text_bounds_total(&self, _ctx: &mut Self::Context) -> TextBounds {
623        let len = UNDEFINED_STR.len();
624        TextBounds::new(len, len)
625    }
626}
627impl Formattable<'_, '_> for OperatorApplicationError {
628    type Context = ValueFormattingOpts; // is_stream_value
629    fn format<W: MaybeTextWrite + ?Sized>(
630        &self,
631        opts: &mut Self::Context,
632        w: &mut W,
633    ) -> std::io::Result<()> {
634        let sv = match opts.type_repr_format {
635            TypeReprFormat::Regular => unreachable!(),
636            TypeReprFormat::Typed => "",
637            TypeReprFormat::Debug => {
638                if opts.is_stream_value {
639                    "~"
640                } else {
641                    ""
642                }
643            }
644        };
645        w.write_text_fmt(format_args!("{sv}(error)\"")).unwrap();
646        let mut ew = EscapedWriter::new(w, b'"');
647        TextWrite::write_all_text(&mut ew, self.message())?;
648        ew.into_inner().unwrap().write_all_text("\"")
649    }
650}
651
652impl Formattable<'_, '_> for dyn CustomData {
653    type Context = RealizedFormatKey;
654
655    fn format<W: MaybeTextWrite + ?Sized>(
656        &self,
657        ctx: &mut Self::Context,
658        w: &mut W,
659    ) -> std::io::Result<()> {
660        CustomData::format_raw(self, &mut TextWriteIoAdapter(w), ctx)
661    }
662
663    fn refuses_truncation(&self, _ctx: &mut Self::Context) -> bool {
664        true // TODO
665    }
666
667    fn total_length_cheap(&self, _ctx: &mut Self::Context) -> bool {
668        false // TODO
669    }
670}
671
672pub trait WithFormattable {
673    type Result;
674    fn call<'a, 'b, F: Formattable<'a, 'b> + ?Sized>(
675        &mut self,
676        v: &F,
677        ctx: &mut F::Context,
678    ) -> Self::Result;
679}
680
681pub fn with_formattable<'a, 'b: 'a, R>(
682    fc: &mut FormattingContext<'a, 'b>,
683    v: FieldValueRef<'_>,
684    mut with_fmt: impl WithFormattable<Result = R>,
685) -> R {
686    metamatch!(match v {
687        #[expand(T in [Null, Undefined])]
688        FieldValueRef::T => with_fmt.call(&T, &mut ()),
689
690        #[expand((REP, CTX) in [
691            (Int, &mut fc.rfk),
692            (Float, &mut fc.rfk),
693            (Array, fc),
694            (Object, fc),
695            (BigInt, &mut fc.rfk),
696            (BigRational, fc),
697            (Argument, fc),
698            (OpDecl, fc),
699            (Text, &mut fc.value_formatting_opts()),
700            (Bytes, &mut fc.value_formatting_opts()),
701            (Error, &mut fc.value_formatting_opts()),
702        ])]
703        FieldValueRef::REP(v) => with_fmt.call(v, CTX),
704
705        FieldValueRef::Custom(v) => with_fmt.call(&**v, &mut fc.rfk),
706
707        #[expand(REP in [
708            StreamValueId, FieldReference, SlicedFieldReference
709        ])]
710        FieldValueRef::REP(_) => {
711            todo!()
712        }
713    })
714}
715
716impl<'a, 'b: 'a> Formattable<'a, 'b> for FieldValueRef<'_> {
717    type Context = FormattingContext<'a, 'b>;
718    fn format<W: MaybeTextWrite + ?Sized>(
719        &self,
720        opts: &mut Self::Context,
721        w: &mut W,
722    ) -> std::io::Result<()> {
723        struct Format<'a, W: ?Sized>(&'a mut W);
724        impl<'a, W: MaybeTextWrite + ?Sized> WithFormattable for Format<'a, W> {
725            type Result = std::io::Result<()>;
726            fn call<'x, 'y, F: Formattable<'x, 'y> + ?Sized>(
727                &mut self,
728                v: &F,
729                ctx: &mut F::Context,
730            ) -> Self::Result {
731                v.format(ctx, self.0)
732            }
733        }
734        with_formattable(opts, *self, Format(w))
735    }
736
737    fn refuses_truncation(&self, opts: &mut Self::Context) -> bool {
738        struct RefusesTruncation;
739        impl WithFormattable for RefusesTruncation {
740            type Result = bool;
741            fn call<'x, 'y, F: Formattable<'x, 'y> + ?Sized>(
742                &mut self,
743                v: &F,
744                ctx: &mut F::Context,
745            ) -> Self::Result {
746                v.refuses_truncation(ctx)
747            }
748        }
749        with_formattable(opts, *self, RefusesTruncation)
750    }
751
752    fn total_length_cheap(&self, opts: &mut Self::Context) -> bool {
753        struct TotalLengthCheap;
754        impl WithFormattable for TotalLengthCheap {
755            type Result = bool;
756            fn call<'x, 'y, F: Formattable<'x, 'y> + ?Sized>(
757                &mut self,
758                v: &F,
759                ctx: &mut F::Context,
760            ) -> Self::Result {
761                v.total_length_cheap(ctx)
762            }
763        }
764        with_formattable(opts, *self, TotalLengthCheap)
765    }
766
767    fn length_total(&self, opts: &mut Self::Context) -> usize {
768        struct LengthTotal;
769        impl WithFormattable for LengthTotal {
770            type Result = usize;
771            fn call<'x, 'y, F: Formattable<'x, 'y> + ?Sized>(
772                &mut self,
773                v: &F,
774                ctx: &mut F::Context,
775            ) -> Self::Result {
776                v.length_total(ctx)
777            }
778        }
779        with_formattable(opts, *self, LengthTotal)
780    }
781
782    fn text_bounds_total(&self, opts: &mut Self::Context) -> TextBounds {
783        struct TextBoundsTotal;
784        impl WithFormattable for TextBoundsTotal {
785            type Result = TextBounds;
786            fn call<'x, 'y, F: Formattable<'x, 'y> + ?Sized>(
787                &mut self,
788                v: &F,
789                ctx: &mut F::Context,
790            ) -> Self::Result {
791                v.text_bounds_total(ctx)
792            }
793        }
794        with_formattable(opts, *self, TextBoundsTotal)
795    }
796
797    fn char_bound_text_bounds(
798        &self,
799        opts: &mut Self::Context,
800        max_chars: usize,
801    ) -> TextBounds {
802        struct CharBoundTextBounds {
803            max_chars: usize,
804        }
805        impl WithFormattable for CharBoundTextBounds {
806            type Result = TextBounds;
807            fn call<'x, 'y, F: Formattable<'x, 'y> + ?Sized>(
808                &mut self,
809                v: &F,
810                ctx: &mut F::Context,
811            ) -> Self::Result {
812                v.char_bound_text_bounds(ctx, self.max_chars)
813            }
814        }
815        with_formattable(opts, *self, CharBoundTextBounds { max_chars })
816    }
817}
818
819impl<'a, 'b: 'a> Formattable<'a, 'b> for Argument {
820    type Context = FormattingContext<'a, 'b>;
821
822    fn format<W: MaybeTextWrite + ?Sized>(
823        &self,
824        ctx: &mut Self::Context,
825        w: &mut W,
826    ) -> std::io::Result<()> {
827        self.value.as_ref().format(ctx, w)
828    }
829
830    fn refuses_truncation(&self, ctx: &mut Self::Context) -> bool {
831        self.value.as_ref().refuses_truncation(ctx)
832    }
833
834    fn total_length_cheap(&self, ctx: &mut Self::Context) -> bool {
835        self.value.as_ref().total_length_cheap(ctx)
836    }
837
838    fn length_total(&self, ctx: &mut Self::Context) -> usize {
839        self.value.as_ref().length_total(ctx)
840    }
841
842    fn text_bounds_total(&self, ctx: &mut Self::Context) -> TextBounds {
843        self.value.as_ref().text_bounds_total(ctx)
844    }
845
846    fn char_bound_text_bounds(
847        &self,
848        ctx: &mut Self::Context,
849        max_chars: usize,
850    ) -> TextBounds {
851        self.value.as_ref().char_bound_text_bounds(ctx, max_chars)
852    }
853}
854
855impl<'a, 'b> Formattable<'a, 'b> for StreamValue<'_> {
856    type Context = ValueFormattingOpts;
857
858    fn format<W: MaybeTextWrite + ?Sized>(
859        &self,
860        ctx: &mut Self::Context,
861        mut w: &mut W,
862    ) -> std::io::Result<()> {
863        if let Some(e) = &self.error {
864            return e.format(ctx, w);
865        }
866
867        let typed = ctx.type_repr_format.is_typed();
868        if ctx.type_repr_format == TypeReprFormat::Debug {
869            w.write_all_text("~")?;
870        }
871        if typed {
872            match self.data_type.unwrap() {
873                StreamValueDataType::Text | StreamValueDataType::MaybeText => {
874                    w.write_all_text("\"")?
875                }
876                StreamValueDataType::Bytes => w.write_all_text("b\"")?,
877                StreamValueDataType::VariableTypeArray
878                | StreamValueDataType::FixedTypeArray(_)
879                | StreamValueDataType::SingleValue(_) => {
880                    todo!()
881                }
882            }
883        }
884        fn write_parts(
885            this: &StreamValue,
886            w: &mut (impl MaybeTextWrite + ?Sized),
887        ) -> std::io::Result<()> {
888            for part in &this.data {
889                match part {
890                    StreamValueData::StaticText(t) => {
891                        w.write_all_text(t)?;
892                    }
893                    StreamValueData::StaticBytes(b) => w.write_all(b)?,
894                    StreamValueData::Text { data, range } => {
895                        w.write_all_text(&data[range.clone()])?
896                    }
897                    StreamValueData::Bytes { data, range } => {
898                        w.write_all(&data[range.clone()])?
899                    }
900                    StreamValueData::Single(_) => todo!(),
901                }
902            }
903            Ok(())
904        }
905        if typed {
906            let mut w_esc = EscapedWriter::new(w, b'"');
907            write_parts(self, &mut w_esc)?;
908            w = w_esc.into_inner()?;
909        } else {
910            write_parts(self, w)?;
911        }
912
913        if typed && self.done {
914            w.write_all_text("\"")?;
915        }
916        Ok(())
917    }
918}
919
920pub fn calc_fmt_layout<'a, 'b, F: Formattable<'a, 'b> + ?Sized>(
921    ctx: &mut F::Context,
922    min_chars: usize,
923    max_chars: usize,
924    formatable: &F,
925) -> TextLayout {
926    if max_chars == usize::MAX || formatable.refuses_truncation(ctx) {
927        if formatable.total_length_cheap(ctx) {
928            let text_len = formatable.length_total(ctx);
929            if (text_len / MAX_UTF8_CHAR_LEN) >= min_chars {
930                return TextLayout::new(text_len, 0);
931            }
932        }
933        let tb = formatable.text_bounds_total(ctx);
934        if tb.char_count >= min_chars {
935            return TextLayout::new(tb.len, 0);
936        }
937        return TextLayout::new(tb.len, min_chars - tb.char_count);
938    }
939    if min_chars > max_chars {
940        let tb = formatable.char_bound_text_bounds(ctx, max_chars);
941        // we might have min_chars = 1, max_chars = 0
942        // so we need the saturating sub
943        return TextLayout::new(
944            tb.len,
945            min_chars.saturating_sub(tb.char_count),
946        );
947    }
948    let tb = formatable.char_bound_text_bounds(ctx, max_chars);
949    if tb.char_count >= max_chars {
950        return TextLayout::new(tb.len, 0);
951    }
952    TextLayout::new(tb.len, min_chars.saturating_sub(tb.char_count))
953}
954
955pub fn format_bytes(w: &mut impl TextWrite, v: &[u8]) -> std::io::Result<()> {
956    w.write_all_text("b\"")?;
957    let mut w = EscapedWriter::new(w, b'"');
958    std::io::Write::write_all(&mut w, v)?;
959    w.into_inner().unwrap().write_all_text("\"")?;
960    Ok(())
961}
962pub fn format_bytes_raw(
963    w: &mut impl std::io::Write,
964    v: &[u8],
965) -> std::io::Result<()> {
966    // TODO: proper raw encoding
967    format_bytes(&mut TextWriteIoAdapter(w), v)
968}
969
970pub fn format_quoted_string_raw(
971    w: &mut (impl TextWrite + ?Sized),
972    v: &str,
973) -> std::io::Result<()> {
974    w.write_all_text("\"")?;
975    let mut w = EscapedWriter::new(w, b'"');
976    std::io::Write::write_all(&mut w, v.as_bytes())?;
977    w.into_inner().unwrap().write_all_text("\"")?;
978    Ok(())
979}
980
981pub fn format_error_raw(
982    w: &mut impl TextWrite,
983    v: &OperatorApplicationError,
984) -> std::io::Result<()> {
985    w.write_text_fmt(format_args!("(\"error\")\"{v}\""))
986}
987
988impl<'a, 'b> FormattingContext<'a, 'b> {
989    pub fn value_formatting_opts(&self) -> ValueFormattingOpts {
990        ValueFormattingOpts {
991            is_stream_value: self.is_stream_value,
992            type_repr_format: self.rfk.opts.type_repr,
993        }
994    }
995    pub fn nested_value_formatting_opts(&self) -> ValueFormattingOpts {
996        ValueFormattingOpts {
997            is_stream_value: false,
998            type_repr_format: TypeReprFormat::Typed,
999        }
1000    }
1001    pub fn for_nested_values<T>(
1002        &mut self,
1003        f: impl FnOnce(&mut FormattingContext) -> T,
1004    ) -> T {
1005        let sv = self.is_stream_value;
1006        self.is_stream_value = false;
1007        let tr = self.rfk.opts.type_repr;
1008        self.rfk.opts.type_repr = TypeReprFormat::Typed;
1009        let res = f(self);
1010        self.rfk.opts.type_repr = tr;
1011        self.is_stream_value = sv;
1012        res
1013    }
1014}
1015
1016pub fn format_rational(
1017    w: &mut (impl TextWrite + ?Sized),
1018    v: &BigRational,
1019    mode: RationalsPrintMode,
1020) -> std::io::Result<()> {
1021    match mode {
1022        RationalsPrintMode::Cutoff(decimals) => {
1023            format_rational_as_decimals_raw(w, v, decimals)
1024        }
1025        RationalsPrintMode::Raw => w.write_text_fmt(format_args!("{}", v)),
1026        RationalsPrintMode::Dynamic => {
1027            let v = v.reduced();
1028            let (_, mut denom) = v.clone().into_raw();
1029            let five = BigInt::from_u32(5).unwrap();
1030            let two = BigInt::from_u32(2).unwrap();
1031            // rational is printable iff it's reduced denominator
1032            // only contains the prime factors two and five
1033            // PERF: :(
1034            while !denom.is_one()
1035                && !denom.is_zero()
1036                && denom.clone().rem(&five).is_zero()
1037            {
1038                denom.div_assign(&five);
1039            }
1040            while !denom.is_one()
1041                && !denom.is_zero()
1042                && denom.clone().rem(&two).is_zero()
1043            {
1044                denom.div_assign(&two);
1045            }
1046            if denom.is_one() {
1047                format_rational_as_decimals_raw(w, &v, u32::MAX)
1048            } else {
1049                w.write_text_fmt(format_args!("{}", &v))
1050            }
1051        }
1052    }
1053}
1054
1055pub fn format_rational_as_decimals_raw(
1056    w: &mut (impl TextWrite + ?Sized),
1057    v: &BigRational,
1058    mut decimals: u32,
1059) -> std::io::Result<()> {
1060    // PERF: this function is stupid
1061    if v.is_integer() {
1062        w.write_text_fmt(format_args!("{v}"))?;
1063        return Ok(());
1064    }
1065    let negative = v.is_negative();
1066    let mut whole_number = v.to_integer();
1067    let mut v = v.sub(&whole_number).abs();
1068    let one = BigInt::one();
1069    let one_half = BigRational::new(one.clone(), BigInt::from_u8(2).unwrap());
1070    if decimals == 0 {
1071        if v >= one_half {
1072            whole_number.add_assign(if negative { -one } else { one });
1073        }
1074        w.write_text_fmt(format_args!("{whole_number}"))?;
1075        return Ok(());
1076    }
1077    w.write_text_fmt(format_args!("{}.", &whole_number))?;
1078
1079    // PERF: really bad
1080    let ten = BigInt::from_u64(10).unwrap();
1081    while decimals > 1 && !v.is_zero() {
1082        v.mul_assign(&ten);
1083        let int_part = v.to_integer();
1084        w.write_text_fmt(format_args!("{}", v.to_u8().unwrap()))?;
1085        v.sub_assign(int_part);
1086        decimals -= 1;
1087    }
1088
1089    if !v.is_zero() {
1090        let round_up = v > one_half;
1091        v.mul_assign(&ten);
1092        if round_up {
1093            v.add_assign(&one);
1094        }
1095        let int_part = v.to_u8().unwrap();
1096        w.write_text_fmt(format_args!("{int_part}"))?;
1097    }
1098    Ok(())
1099}
1100
1101#[cfg(test)]
1102mod test {
1103    use num::{BigInt, BigRational};
1104    use rstest::rstest;
1105
1106    use crate::options::chain_settings::RationalsPrintMode;
1107
1108    use super::format_rational;
1109
1110    #[rstest]
1111    #[case(1, 3, "1/3")]
1112    #[case(1, 2, "0.5")]
1113    #[case(1, 7, "1/7")]
1114    #[case(1, 42, "1/42")]
1115    #[case(1234, 1000, "1.234")]
1116    #[case(1234, -1000, "-1.234")]
1117    fn print_dynamic_fraction(
1118        #[case] num: i64,
1119        #[case] denom: i64,
1120        #[case] output: &str,
1121    ) {
1122        let mut res = String::new();
1123        format_rational(
1124            &mut res,
1125            &BigRational::new(BigInt::from(num), BigInt::from(denom)),
1126            RationalsPrintMode::Dynamic,
1127        )
1128        .unwrap();
1129        assert_eq!(res, output)
1130    }
1131}