Skip to main content

deser_toml/
ser.rs

1use std::borrow::Cow;
2use std::fmt::Write;
3
4use deser_core::ext::ExtValue;
5use deser_core::hints::Layout;
6use deser_core::ser::{self, SerializeDriver, SerializeRef};
7use deser_core::{Atom, BytesFormat, Error, ErrorKind, Event, Serialize, State};
8
9use crate::document::{Document, Entry, Item, Span, TableKind, Value};
10use deser_core::ext::{Datetime, Number, Timestamp};
11
12/// Configures how values are serialized to TOML.
13///
14/// The value has to serialize to a map (for instance a struct or a map
15/// type) as TOML documents are tables.  As TOML has no null value, map
16/// entries with null values (such as `None`) are skipped, null values in
17/// sequences are an error.
18///
19/// Values that are maps are written as `[table]` sections and sequences of
20/// maps as `[[array]]` sections unless they are nested in other sequences or
21/// have the [`Layout::Compact`](deser_core::hints::Layout) hint (see
22/// [`hints`](deser_core::hints)) which makes them inline.  Inline tables and
23/// inline arrays of tables have that hint when deserialized, so they stay
24/// inline when a value is deserialized and serialized again.
25/// The output is compatible with TOML 1.0.
26///
27/// [`to_string`](Self::to_string) works like the
28/// [`to_string`] function.
29#[derive(Debug, Clone, Default, PartialEq, Eq)]
30pub struct SerializerConfig {
31    context: deser_core::Context,
32}
33
34impl SerializerConfig {
35    /// Creates the default configuration.
36    pub const fn new() -> SerializerConfig {
37        SerializerConfig {
38            context: deser_core::Context::new(),
39        }
40    }
41
42    /// Returns a builder for the configuration (see [`SerializerConfigBuilder`]).
43    pub const fn builder() -> SerializerConfigBuilder {
44        SerializerConfigBuilder::new()
45    }
46
47    /// Returns a builder that starts with this configuration.
48    pub const fn into_builder(self) -> SerializerConfigBuilder {
49        SerializerConfigBuilder { value: self }
50    }
51
52    /// Sets the context the values are serialized in.
53    ///
54    /// The values of the context are the defaults of the extension values
55    /// of the state (see [`Context`](deser_core::Context)), for instance
56    /// the [`BytesFormat`](deser_core::BytesFormat).  The serializers and
57    /// writers created with the configuration use this context.  A context set on
58    /// the driver takes precedence.
59    pub fn set_context(&mut self, context: deser_core::Context) {
60        self.context = context;
61    }
62
63    /// Returns the context the values are serialized in.
64    pub fn context(&self) -> &deser_core::Context {
65        &self.context
66    }
67
68    /// Gives the context to a driver which has none.
69    #[inline]
70    fn apply_context(&self, driver: &mut SerializeDriver<'_>) {
71        if !self.context.is_empty() {
72            driver.set_default_context(self.context.clone());
73        }
74    }
75
76    /// Serializes the given value.
77    pub fn to_string<T: Serialize + ?Sized>(&self, value: &T) -> Result<String, Error> {
78        self.to_string_ref(SerializeRef::new(&value))
79    }
80
81    /// Serializes the given value with a configured driver.
82    ///
83    /// The callback is invoked with the driver before the serialization
84    /// starts, for instance to add [`Layer`](deser_core::ser::Layer)s.
85    pub fn to_string_with<F, T: Serialize + ?Sized>(
86        &self,
87        value: &T,
88        setup: F,
89    ) -> Result<String, Error>
90    where
91        F: FnOnce(&mut SerializeDriver<'_>),
92    {
93        let mut driver = SerializeDriver::new(&value);
94        setup(&mut driver);
95        self.apply_context(&mut driver);
96        self.serialize_driver(&mut driver)
97    }
98
99    /// Serializes a value whose type is erased (see
100    /// [`to_string`](Self::to_string)).
101    ///
102    /// This is not generic: the code that exists for every type only
103    /// erases it.
104    fn to_string_ref(&self, value: SerializeRef<'_>) -> Result<String, Error> {
105        let mut driver = SerializeDriver::from_ref(value);
106        self.apply_context(&mut driver);
107        self.serialize_driver(&mut driver)
108    }
109
110    /// Serializes the value of a driver.
111    pub(crate) fn serialize_driver(
112        &self,
113        driver: &mut SerializeDriver<'_>,
114    ) -> Result<String, Error> {
115        let mut builder = Builder {
116            doc: Document::default(),
117            stack: Vec::new(),
118            done: false,
119            bytes: BytesFormat::of(driver.state()),
120        };
121        driver.drive(|event, state| builder.event(event, state))?;
122        if !builder.done {
123            return Err(Error::new(
124                ErrorKind::InvalidState,
125                "no value was serialized",
126            ));
127        }
128        let mut writer = Writer {
129            doc: &builder.doc,
130            out: String::new(),
131        };
132        writer.write_document()?;
133        Ok(writer.out)
134    }
135}
136
137/// Builds a [`SerializerConfig`].
138///
139/// The methods have the names of the setters of [`SerializerConfig`] (without `set_`).
140#[derive(Debug, Clone)]
141#[must_use]
142pub struct SerializerConfigBuilder {
143    value: SerializerConfig,
144}
145
146impl SerializerConfigBuilder {
147    /// Creates a builder that starts with the default.
148    pub const fn new() -> SerializerConfigBuilder {
149        SerializerConfigBuilder {
150            value: SerializerConfig::new(),
151        }
152    }
153
154    /// Sets the context the values are serialized in.
155    ///
156    /// See [`SerializerConfig::set_context`].
157    pub fn context(mut self, context: deser_core::Context) -> SerializerConfigBuilder {
158        self.value.set_context(context);
159        self
160    }
161
162    /// Returns the built [`SerializerConfig`].
163    pub const fn build(self) -> SerializerConfig {
164        // the value cannot be moved out of the builder in a const fn as the
165        // builder needs dropping (the context has a destructor)
166        // SAFETY: the value is read once and the builder is forgotten
167        let value = unsafe { core::ptr::read(&self.value) };
168        core::mem::forget(self);
169        value
170    }
171}
172
173impl Default for SerializerConfigBuilder {
174    fn default() -> SerializerConfigBuilder {
175        SerializerConfigBuilder::new()
176    }
177}
178
179/// Serializes values into TOML.
180///
181/// A TOML document holds a single value, writing a second one fails.
182///
183/// ```
184/// use std::collections::BTreeMap;
185/// use deser_toml::Serializer;
186///
187/// let mut serializer = Serializer::new();
188/// serializer.serialize(&BTreeMap::from([("a", 1)])).unwrap();
189/// assert!(serializer.serialize(&BTreeMap::from([("b", 2)])).is_err());
190/// assert_eq!(serializer.finish(), "a = 1\n");
191/// ```
192///
193/// The serializer is also the stream serializer of TOML (see
194/// [`StreamSerializer`](ser::StreamSerializer)).  A TOML document cannot be
195/// written in parts: the values of a table come before its subtables.  To
196/// write to a [`Write`](std::io::Write) use [`SerializerConfig::writer`].
197#[derive(Debug, Clone)]
198pub struct Serializer {
199    config: SerializerConfig,
200    out: String,
201    written: usize,
202}
203
204impl Default for Serializer {
205    fn default() -> Serializer {
206        Serializer::new()
207    }
208}
209
210impl Serializer {
211    /// Creates a serializer.
212    pub fn new() -> Serializer {
213        Serializer::with_config(SerializerConfig::new())
214    }
215
216    /// Creates a serializer with the given configuration.
217    pub fn with_config(config: SerializerConfig) -> Serializer {
218        Serializer {
219            config,
220            out: String::new(),
221            written: 0,
222        }
223    }
224
225    /// Serializes a value.
226    ///
227    /// If the value fails to serialize, nothing is written.
228    pub fn serialize<T: Serialize + ?Sized>(&mut self, value: &T) -> Result<(), Error> {
229        ser::Serializer::serialize(self, value)
230    }
231
232    /// Serializes a value with a configured driver.
233    ///
234    /// The callback is invoked with the driver before the value is
235    /// serialized, for instance to add [`Layer`](deser_core::ser::Layer)s.
236    pub fn serialize_with<F, T: Serialize + ?Sized>(
237        &mut self,
238        value: &T,
239        setup: F,
240    ) -> Result<(), Error>
241    where
242        F: FnOnce(&mut SerializeDriver<'_>),
243    {
244        ser::Serializer::serialize_with(self, value, setup)
245    }
246
247    /// Returns the configuration.
248    pub fn config(&self) -> &SerializerConfig {
249        &self.config
250    }
251
252    /// Returns the output written so far (that was not cleared).
253    pub fn as_str(&self) -> &str {
254        &self.out
255    }
256
257    /// Returns the output.
258    pub fn finish(self) -> String {
259        self.out
260    }
261}
262
263impl ser::Serializer for Serializer {
264    fn drive(&mut self, driver: &mut SerializeDriver<'_>) -> Result<(), Error> {
265        if !self.config.context.is_empty() {
266            driver.set_default_context(self.config.context.clone());
267        }
268        if self.written > 0 {
269            return Err(Error::new(
270                ErrorKind::InvalidState,
271                "a TOML document holds a single value",
272            ));
273        }
274        let toml = self.config.serialize_driver(driver)?;
275        self.out.push_small(&toml);
276        self.written += 1;
277        Ok(())
278    }
279}
280
281impl ser::StreamSerializer for Serializer {
282    fn output(&self) -> &[u8] {
283        self.out.as_bytes()
284    }
285
286    fn clear_output(&mut self) {
287        self.out.clear();
288    }
289}
290
291#[cfg(feature = "io")]
292impl SerializerConfig {
293    /// Creates a writer of a TOML document (see
294    /// [`deser::io::Writer`](deser_core::io::Writer)).
295    ///
296    /// A stream holds a single document, writing a second value fails.  The
297    /// document is written with a single write once it's complete.
298    pub fn writer<W: std::io::Write>(&self, writer: W) -> deser_core::io::Writer<W, Serializer> {
299        deser_core::io::Writer::new(writer, Serializer::with_config(self.clone()))
300    }
301
302    /// Serializes a value to a writer.
303    ///
304    /// See [`to_writer`].
305    pub fn to_writer<W: std::io::Write, T: Serialize + ?Sized>(
306        &self,
307        writer: W,
308        value: &T,
309    ) -> Result<(), Error> {
310        self.writer(writer).write(value)
311    }
312}
313
314/// Serializes a value to a writer.
315///
316/// The document is written with a single write once it's complete: TOML
317/// documents cannot be written while the value is serialized as the values
318/// of a table come before its subtables.
319///
320/// ```
321/// use std::collections::BTreeMap;
322///
323/// let mut out = Vec::new();
324/// deser_toml::to_writer(&mut out, &BTreeMap::from([("a", 1)])).unwrap();
325/// assert_eq!(out, b"a = 1\n");
326/// ```
327#[cfg(feature = "io")]
328pub fn to_writer<W: std::io::Write, T: Serialize + ?Sized>(
329    writer: W,
330    value: &T,
331) -> Result<(), Error> {
332    SerializerConfig::new().to_writer(writer, value)
333}
334
335/// Serializes a value to TOML.
336///
337/// This uses the default [`SerializerConfig`], see there for more
338/// information.
339///
340/// ```
341/// use std::collections::BTreeMap;
342///
343/// let mut value = BTreeMap::new();
344/// value.insert("name", vec!["a", "b"]);
345/// assert_eq!(
346///     deser_toml::to_string(&value).unwrap(),
347///     "name = [\"a\", \"b\"]\n"
348/// );
349/// ```
350pub fn to_string<T: Serialize + ?Sized>(value: &T) -> Result<String, Error> {
351    SerializerConfig::new().to_string(value)
352}
353
354/// A map or sequence that is being built.
355enum Frame {
356    /// A table with the pending key if the next event is a value.
357    Table(usize, Option<String>),
358    Array(usize),
359}
360
361/// Builds a document from serialization events.
362struct Builder {
363    doc: Document<'static>,
364    stack: Vec<Frame>,
365    done: bool,
366    bytes: BytesFormat,
367}
368
369/// A value converted from an atom.
370enum Converted {
371    Value(Value<'static>),
372    Null,
373}
374
375impl Builder {
376    fn event(&mut self, event: Event, state: &State) -> Result<(), Error> {
377        let Some(frame) = self.stack.last_mut() else {
378            if self.done {
379                return Err(Error::new(ErrorKind::InvalidState, "unexpected event"));
380            }
381            return match event {
382                Event::MapStart(_) => {
383                    let id = self.doc.new_table(TableKind::Header, Span::default());
384                    self.stack.push(Frame::Table(id, None));
385                    Ok(())
386                }
387                _ => Err(Error::new(
388                    ErrorKind::UnsupportedType,
389                    "TOML documents must be tables",
390                )),
391            };
392        };
393
394        match *frame {
395            Frame::Table(_, ref mut key @ None) => match event {
396                Event::Atom(atom) => {
397                    *key = Some(key_to_string(atom, self.bytes)?);
398                    Ok(())
399                }
400                Event::MapEnd => {
401                    self.stack.pop();
402                    self.done = self.stack.is_empty();
403                    Ok(())
404                }
405                _ => Err(unsupported_key()),
406            },
407            Frame::Table(id, ref mut key @ Some(_)) => {
408                let key = key.take().unwrap();
409                let value = match self.value(event, state)? {
410                    Converted::Value(value) => value,
411                    // map entries with null values are skipped
412                    Converted::Null => return Ok(()),
413                };
414                let entry = Entry {
415                    key: Cow::Owned(key),
416                    key_span: Span::default(),
417                    item: Item {
418                        value,
419                        span: Span::default(),
420                    },
421                };
422                match self.doc.insert_new(id, entry) {
423                    Ok(()) => Ok(()),
424                    Err(entry) => Err(Error::new(
425                        ErrorKind::DuplicateKey,
426                        format!("duplicate key `{}`", entry.key),
427                    )),
428                }
429            }
430            Frame::Array(id) => {
431                if event == Event::SeqEnd {
432                    self.stack.pop();
433                    return Ok(());
434                }
435                match self.value(event, state)? {
436                    Converted::Value(value) => {
437                        self.doc.arrays[id].items.push(Item {
438                            value,
439                            span: Span::default(),
440                        });
441                        Ok(())
442                    }
443                    Converted::Null => Err(Error::new(
444                        ErrorKind::UnsupportedType,
445                        "TOML does not support null values in arrays",
446                    )),
447                }
448            }
449        }
450    }
451
452    /// Converts the first event of a value.  Maps and sequences are pushed
453    /// to the stack.
454    fn value(&mut self, event: Event, state: &State) -> Result<Converted, Error> {
455        match event {
456            Event::Atom(Atom::Bytes(ref bytes)) => Ok(Converted::Value(
457                self.bytes_value(bytes, bytes.fallback.copied().unwrap_or(self.bytes)),
458            )),
459            Event::Atom(atom) => convert_atom(atom, self.bytes),
460            Event::MapStart(_) => {
461                // compact tables are inline tables, others sections
462                let kind = match Layout::of(state) {
463                    Layout::Compact => TableKind::Inline,
464                    _ => TableKind::Header,
465                };
466                let id = self.doc.new_table(kind, Span::default());
467                self.stack.push(Frame::Table(id, None));
468                Ok(Converted::Value(Value::Table(id)))
469            }
470            Event::SeqStart(_) => {
471                // arrays of tables are `[[array]]` sections unless compact
472                let of_tables = Layout::of(state) != Layout::Compact;
473                let id = self.doc.new_array(of_tables, Span::default());
474                self.stack.push(Frame::Array(id));
475                Ok(Converted::Value(Value::Array(id)))
476            }
477            Event::MapEnd | Event::SeqEnd => {
478                Err(Error::new(ErrorKind::InvalidState, "unexpected end event"))
479            }
480        }
481    }
482
483    /// Converts bytes into a string or an array of integers.
484    fn bytes_value(&mut self, bytes: &[u8], format: BytesFormat) -> Value<'static> {
485        match format.encode(bytes) {
486            Some(encoded) => Value::Str(Cow::Owned(encoded)),
487            None => {
488                let id = self.doc.new_array(false, Span::default());
489                self.doc.arrays[id]
490                    .items
491                    .extend(bytes.iter().map(|&byte| Item {
492                        value: Value::Int(byte.into()),
493                        span: Span::default(),
494                    }));
495                Value::Array(id)
496            }
497        }
498    }
499}
500
501fn convert_atom(atom: Atom, bytes: BytesFormat) -> Result<Converted, Error> {
502    Ok(Converted::Value(match atom {
503        Atom::Null => return Ok(Converted::Null),
504        Atom::Bool(value) => Value::Bool(value),
505        Atom::Str(value) | Atom::Lexical(value) => Value::Str(Cow::Owned(value.into_owned())),
506        Atom::Char(value) => Value::Str(Cow::Owned(value.to_string())),
507        Atom::U64(value) => match i64::try_from(value) {
508            Ok(value) => Value::Int(value),
509            Err(_) => Value::UInt(value),
510        },
511        Atom::I64(value) => Value::Int(value),
512        Atom::F64(value) => Value::Float(value),
513        Atom::F32(value) => Value::Float32(value),
514        // bytes are converted by the builder, this is reached for the
515        // fallbacks of extension values which cannot be arrays.
516        Atom::Bytes(value) => Value::Str(Cow::Owned(encode_str(&value, value.fallback, bytes))),
517        Atom::Ext(ref ext) => return convert_ext(ext, bytes),
518        // values whose type was inferred from text are written as value
519        Atom::Implicit(value) => return convert_atom(value.value().to_atom(), bytes),
520        _ => return Err(Error::new(ErrorKind::UnsupportedType, "unknown atom")),
521    }))
522}
523
524#[cold]
525fn convert_ext(ext: &ExtValue, bytes: BytesFormat) -> Result<Converted, Error> {
526    if let Some(value) = ext.downcast_ref::<Datetime>() {
527        if !value.is_valid() {
528            return Err(Error::new(ErrorKind::InvalidValue, "invalid datetime"));
529        }
530        return Ok(Converted::Value(Value::Datetime(*value)));
531    }
532    // numbers from text formats keep their text if it's a float, the syntax
533    // of JSON floats is valid in TOML
534    if let Some(value) = ext.downcast_value_ref::<Number>() {
535        return Ok(Converted::Value(if value.is_integer() {
536            Value::Float(value.value())
537        } else {
538            Value::FloatText(Cow::Owned(value.as_str().to_string()))
539        }));
540    }
541    // instants are written as offset date-times in UTC if possible
542    if let Some(value) = ext
543        .downcast_ref::<Timestamp>()
544        .and_then(|x| x.to_datetime())
545    {
546        return Ok(Converted::Value(Value::Datetime(value)));
547    }
548    let out_of_range = || Error::new(ErrorKind::OutOfRange, "integer out of range for TOML");
549    if let Some(&value) = ext.downcast_ref::<u128>() {
550        let value = u64::try_from(value).map_err(|_| out_of_range())?;
551        return convert_atom(Atom::U64(value), bytes);
552    }
553    if let Some(&value) = ext.downcast_ref::<i128>() {
554        return if let Ok(value) = i64::try_from(value) {
555            convert_atom(Atom::I64(value), bytes)
556        } else {
557            let value = u64::try_from(value).map_err(|_| out_of_range())?;
558            convert_atom(Atom::U64(value), bytes)
559        };
560    }
561    match ext.fallback() {
562        Atom::Ext(_) => Err(Error::new(
563            ErrorKind::UnsupportedType,
564            format!("TOML does not support {}", ext.name()),
565        )),
566        fallback => convert_atom(fallback, bytes),
567    }
568}
569
570/// Encodes bytes as string.
571///
572/// Strings are required (for keys), so bytes that would be arrays are base64.
573fn encode_str(value: &[u8], fallback: Option<&BytesFormat>, bytes: BytesFormat) -> String {
574    fallback
575        .copied()
576        .unwrap_or(bytes)
577        .encode(value)
578        .or_else(|| BytesFormat::BASE64.encode(value))
579        .unwrap_or_default()
580}
581
582fn key_to_string(atom: Atom, bytes: BytesFormat) -> Result<String, Error> {
583    Ok(match atom {
584        Atom::Implicit(value) => return key_to_string(value.value().to_atom(), bytes),
585        Atom::Str(value) | Atom::Lexical(value) => value.into_owned(),
586        Atom::Char(value) => value.to_string(),
587        Atom::U64(value) => value.to_string(),
588        Atom::I64(value) => value.to_string(),
589        Atom::Bool(value) => value.to_string(),
590        Atom::Bytes(value) => encode_str(&value, value.fallback, bytes),
591        Atom::Ext(ref ext) => {
592            if let Some(value) = ext.downcast_ref::<u128>() {
593                value.to_string()
594            } else if let Some(value) = ext.downcast_ref::<i128>() {
595                value.to_string()
596            } else {
597                match ext.fallback() {
598                    Atom::Ext(_) => return Err(unsupported_key()),
599                    fallback => return key_to_string(fallback, bytes),
600                }
601            }
602        }
603        _ => return Err(unsupported_key()),
604    })
605}
606
607#[cold]
608fn unsupported_key() -> Error {
609    Error::new(
610        ErrorKind::UnsupportedType,
611        "TOML only supports strings, integers and booleans as keys",
612    )
613}
614
615/// How a table is introduced.
616#[derive(Clone, Copy, PartialEq, Eq)]
617enum Header {
618    /// The root table.
619    None,
620    /// `[table]`
621    Table,
622    /// `[[table]]`
623    ArrayTable,
624}
625
626/// A table section to write.
627struct Section<'d> {
628    id: usize,
629    path: Vec<&'d str>,
630    header: Header,
631}
632
633/// An inline array or table that is being written.
634enum InlineFrame {
635    Table(usize, usize),
636    Array(usize, usize),
637}
638
639struct Writer<'d> {
640    doc: &'d Document<'static>,
641    out: String,
642}
643
644impl<'d> Writer<'d> {
645    /// Returns `true` if the value is written as section rather than inline.
646    fn is_section(&self, value: &Value) -> bool {
647        match *value {
648            Value::Table(id) => self.doc.tables[id].kind != TableKind::Inline,
649            Value::Array(id) => self.is_array_of_tables(id),
650            _ => false,
651        }
652    }
653
654    fn is_array_of_tables(&self, id: usize) -> bool {
655        let array = &self.doc.arrays[id];
656        array.of_tables
657            && !array.items.is_empty()
658            && array
659                .items
660                .iter()
661                .all(|x| matches!(x.value, Value::Table(_)))
662    }
663
664    fn write_document(&mut self) -> Result<(), Error> {
665        let doc = self.doc;
666        let mut sections = vec![Section {
667            id: 0,
668            path: Vec::new(),
669            header: Header::None,
670        }];
671
672        while let Some(section) = sections.pop() {
673            let table = &doc.tables[section.id];
674            let has_values = table
675                .entries
676                .iter()
677                .any(|x| !self.is_section(&x.item.value));
678
679            // tables which only contain other tables do not need a header,
680            // they are created implicitly by the headers of their children.
681            let header = match section.header {
682                Header::Table if !has_values && !table.entries.is_empty() => None,
683                Header::Table => Some(("[", "]")),
684                Header::ArrayTable => Some(("[[", "]]")),
685                Header::None => None,
686            };
687            if let Some((open, close)) = header {
688                if !self.out.is_empty() {
689                    self.out.push('\n');
690                }
691                self.out.push_small(open);
692                for (idx, key) in section.path.iter().enumerate() {
693                    if idx > 0 {
694                        self.out.push('.');
695                    }
696                    write_key(&mut self.out, key);
697                }
698                self.out.push_small(close);
699                self.out.push('\n');
700            }
701
702            for entry in &table.entries {
703                if !self.is_section(&entry.item.value) {
704                    write_key(&mut self.out, &entry.key);
705                    self.out.push_small(" = ");
706                    self.write_value(&entry.item.value)?;
707                    self.out.push('\n');
708                }
709            }
710
711            // the sections are processed from the end of the stack
712            let first_child = sections.len();
713            for entry in &table.entries {
714                let child_path = || {
715                    let mut path = section.path.clone();
716                    path.push(&*entry.key);
717                    path
718                };
719                match entry.item.value {
720                    Value::Table(id) if self.is_section(&entry.item.value) => {
721                        sections.push(Section {
722                            id,
723                            path: child_path(),
724                            header: Header::Table,
725                        })
726                    }
727                    Value::Array(id) if self.is_array_of_tables(id) => {
728                        for item in &doc.arrays[id].items {
729                            if let Value::Table(id) = item.value {
730                                sections.push(Section {
731                                    id,
732                                    path: child_path(),
733                                    header: Header::ArrayTable,
734                                });
735                            }
736                        }
737                    }
738                    _ => {}
739                }
740            }
741            sections[first_child..].reverse();
742        }
743
744        Ok(())
745    }
746
747    /// Writes a value inline.
748    fn write_value(&mut self, value: &Value) -> Result<(), Error> {
749        let doc = self.doc;
750        let mut stack = Vec::new();
751        self.write_value_start(value, &mut stack);
752
753        while let Some(frame) = stack.last_mut() {
754            let value = match *frame {
755                InlineFrame::Table(id, ref mut index) => {
756                    let table = &doc.tables[id];
757                    match table.entries.get(*index) {
758                        Some(entry) => {
759                            self.out.push_small(if *index == 0 { " " } else { ", " });
760                            *index += 1;
761                            write_key(&mut self.out, &entry.key);
762                            self.out.push_small(" = ");
763                            &entry.item.value
764                        }
765                        None => {
766                            self.out
767                                .push_small(if table.entries.is_empty() { "}" } else { " }" });
768                            stack.pop();
769                            continue;
770                        }
771                    }
772                }
773                InlineFrame::Array(id, ref mut index) => match doc.arrays[id].items.get(*index) {
774                    Some(item) => {
775                        if *index > 0 {
776                            self.out.push_small(", ");
777                        }
778                        *index += 1;
779                        &item.value
780                    }
781                    None => {
782                        self.out.push(']');
783                        stack.pop();
784                        continue;
785                    }
786                },
787            };
788            self.write_value_start(value, &mut stack);
789        }
790
791        Ok(())
792    }
793
794    /// Writes a scalar or the start of an inline table or array.
795    fn write_value_start(&mut self, value: &Value, stack: &mut Vec<InlineFrame>) {
796        match *value {
797            Value::Str(ref value) => write_string(&mut self.out, value),
798            Value::Int(value) => self.out.push_small(itoa::Buffer::new().format(value)),
799            Value::UInt(value) => self.out.push_small(itoa::Buffer::new().format(value)),
800            Value::Float(value) => write_float(&mut self.out, value),
801            Value::Float32(value) => write_float(&mut self.out, value),
802            Value::FloatText(ref value) => self.out.push_small(value),
803            Value::Bool(value) => self.out.push_small(if value { "true" } else { "false" }),
804            Value::Datetime(ref value) => write!(self.out, "{}", value).unwrap(),
805            Value::Table(id) => {
806                self.out.push('{');
807                stack.push(InlineFrame::Table(id, 0));
808            }
809            Value::Array(id) => {
810                self.out.push('[');
811                stack.push(InlineFrame::Array(id, 0));
812            }
813        }
814    }
815}
816
817/// Writes a float with the shortest text that reads back as the same value
818/// of its type (`f32` or `f64`).  The text always has a fractional part or
819/// an exponent.
820fn write_float<F: zmij::Float + Into<f64>>(out: &mut String, value: F) {
821    let wide: f64 = value.into();
822    if wide.is_nan() {
823        out.push_small("nan");
824    } else if wide.is_infinite() {
825        out.push_small(if wide > 0.0 { "inf" } else { "-inf" });
826    } else {
827        out.push_small(zmij::Buffer::new().format_finite(value));
828    }
829}
830
831fn is_bare_key(key: &str) -> bool {
832    !key.is_empty()
833        && key
834            .bytes()
835            .all(|c| c.is_ascii_alphanumeric() || c == b'_' || c == b'-')
836}
837
838fn write_key(out: &mut String, key: &str) {
839    if is_bare_key(key) {
840        out.push_small(key);
841    } else {
842        write_basic_string(out, key);
843    }
844}
845
846/// Writes a string as basic, literal or multi-line basic string.
847fn write_string(out: &mut String, value: &str) {
848    if value.contains('\n') {
849        write_multiline_string(out, value);
850    } else if value.contains(['"', '\\'])
851        && !value
852            .chars()
853            .any(|c| c == '\'' || (c.is_control() && c != '\t'))
854    {
855        // literal strings do not need escaping for quotes and backslashes
856        out.push('\'');
857        out.push_small(value);
858        out.push('\'');
859    } else {
860        write_basic_string(out, value);
861    }
862}
863
864/// Writes an escape for a control character.
865///
866/// Only escapes that exist in TOML 1.0 are used.
867fn write_control_escape(out: &mut String, c: char) {
868    match c {
869        '\x08' => out.push_small("\\b"),
870        '\t' => out.push_small("\\t"),
871        '\n' => out.push_small("\\n"),
872        '\x0c' => out.push_small("\\f"),
873        '\r' => out.push_small("\\r"),
874        c => write!(out, "\\u{:04X}", c as u32).unwrap(),
875    }
876}
877
878fn is_escaped_control(c: char) -> bool {
879    matches!(c, '\0'..='\x1f' | '\x7f')
880}
881
882fn write_basic_string(out: &mut String, value: &str) {
883    out.push('"');
884    for c in value.chars() {
885        match c {
886            '"' => out.push_small("\\\""),
887            '\\' => out.push_small("\\\\"),
888            c if is_escaped_control(c) => write_control_escape(out, c),
889            c => out.push(c),
890        }
891    }
892    out.push('"');
893}
894
895fn write_multiline_string(out: &mut String, value: &str) {
896    // the newline after the opening delimiter is trimmed by parsers
897    out.push_small("\"\"\"\n");
898    let mut quotes = 0;
899    for c in value.chars() {
900        match c {
901            // three quotes in a row would end the string
902            '"' if quotes == 2 => {
903                out.push_small("\\\"");
904                quotes = 0;
905                continue;
906            }
907            '"' => out.push('"'),
908            '\\' => out.push_small("\\\\"),
909            '\n' | '\t' => out.push(c),
910            c if is_escaped_control(c) => write_control_escape(out, c),
911            c => out.push(c),
912        }
913        quotes = if c == '"' { quotes + 1 } else { 0 };
914    }
915    out.push_small("\"\"\"");
916}
917
918/// Appends short strings without calling into `memcpy`.
919trait PushSmall {
920    fn push_small(&mut self, s: &str);
921}
922
923impl PushSmall for String {
924    #[inline(always)]
925    fn push_small(&mut self, s: &str) {
926        crate::copy::push_str(self, s);
927    }
928}