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#[rustfmt::skip]
49#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
66pub enum NumberFormat {
67 #[default]
68 Default, Binary, BinaryZeroB, Octal, OctalZeroO, LowerHex, UpperHex, LowerHexZeroX, UpperHexZeroX, LowerExp, UpperExp, }
80
81#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
82pub enum TypeReprFormat {
83 #[default]
84 Regular, Typed, Debug, }
88
89#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
90pub enum PrettyPrintFormat {
91 #[default]
92 Regular, Pretty, Compact, }
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 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 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 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; 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 }
666
667 fn total_length_cheap(&self, _ctx: &mut Self::Context) -> bool {
668 false }
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 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 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 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 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 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}