math-core-renderer-internal 0.6.2

Internal crate used by math-core
Documentation
use std::fmt::Debug;

use stable_arena::DroplessArena;

use crate::{
    ast::{AHref, Node},
    super_char::SuperChar,
    table::{ArraySpec, ColumnSpec, RowLabelInfo},
};

pub struct Arena {
    inner: DroplessArena,
}

impl Arena {
    pub fn new() -> Self {
        Arena {
            inner: DroplessArena::default(),
        }
    }

    pub fn push<'arena>(&'arena self, node: Node<'arena>) -> &'arena mut Node<'arena> {
        self.inner.alloc(node)
    }

    pub fn push_slice<'arena>(
        &'arena self,
        nodes: &[&'arena Node<'arena>],
    ) -> &'arena [&'arena Node<'arena>] {
        // `DroplessArena::alloc_slice()` panics on empty slices.
        if nodes.is_empty() {
            &[]
        } else {
            self.inner.alloc_slice(nodes)
        }
    }

    pub fn alloc_str(&self, src: &str) -> &str {
        // `DroplessArena::alloc_str()` panics on empty strings.
        if src.is_empty() {
            ""
        } else {
            self.inner.alloc_str(src)
        }
    }

    pub fn alloc_column_specs<'arena>(
        &'arena self,
        column_specs: &[ColumnSpec],
    ) -> &'arena [ColumnSpec] {
        // `DroplessArena::alloc_slice()` panics on empty slices.
        if column_specs.is_empty() {
            &[]
        } else {
            self.inner.alloc_slice(column_specs)
        }
    }

    pub fn alloc_array_spec<'arena>(
        &'arena self,
        array_spec: ArraySpec<'arena>,
    ) -> &'arena ArraySpec<'arena> {
        self.inner.alloc(array_spec)
    }

    pub fn alloc_row_label_info<'arena>(
        &'arena self,
        info: RowLabelInfo<'arena>,
    ) -> &'arena RowLabelInfo<'arena> {
        self.inner.alloc(info)
    }

    pub fn alloc_ahref<'arena, 'attrs>(
        &'arena self,
        ahref: AHref<'attrs>,
    ) -> &'arena AHref<'attrs> {
        self.inner.alloc(ahref)
    }
}

impl Default for Arena {
    fn default() -> Self {
        Self::new()
    }
}

#[derive(Debug)]
#[repr(transparent)]
pub struct Buffer(String);

impl Buffer {
    pub fn new(size_hint: usize) -> Self {
        Buffer(String::with_capacity(size_hint))
    }

    pub fn get_builder(&mut self) -> StringBuilder<'_> {
        StringBuilder::new(self)
    }
}

/// A helper type to safely build a string in the buffer from multiple pieces.
///
/// It takes an exclusive reference to the buffer and clears everything in the
/// buffer before we start building. This guarantees that upon finishing, the
/// buffer contains only what we wrote to it.
#[derive(Debug)]
pub struct StringBuilder<'buffer> {
    buffer: &'buffer mut Buffer,
}

impl<'buffer> StringBuilder<'buffer> {
    pub fn new(buffer: &'buffer mut Buffer) -> Self {
        // Clear the buffer before we start building.
        buffer.0.clear();
        StringBuilder { buffer }
    }

    #[inline]
    pub fn push_str(&mut self, src: &str) {
        self.buffer.0.push_str(src)
    }

    pub fn push_char(&mut self, c: char) {
        self.buffer.0.push(c)
    }

    #[inline]
    pub fn push_superchar(&mut self, sc: SuperChar) {
        self.buffer.0.extend(sc.chars());
    }

    pub fn finish(self, arena: &Arena) -> &str {
        arena.alloc_str(&self.buffer.0)
    }

    pub fn is_empty(&self) -> bool {
        self.buffer.0.is_empty()
    }
}

impl std::fmt::Write for StringBuilder<'_> {
    #[inline]
    fn write_str(&mut self, s: &str) -> std::fmt::Result {
        self.push_str(s);
        Ok(())
    }

    #[inline]
    fn write_char(&mut self, c: char) -> std::fmt::Result {
        self.push_char(c);
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_arena() {
        let arena = Arena::new();
        let node = Node::HardcodedMathML("Hello, world!");
        let reference = arena.push(node);
        assert!(matches!(reference, Node::HardcodedMathML("Hello, world!")));
    }

    #[test]
    fn test_buffer_extend() {
        let arena = Arena::new();
        let mut buffer = Buffer::new(0);
        let mut builder = buffer.get_builder();
        builder.push_char('H');
        builder.push_char('i');
        let str_ref = builder.finish(&arena);
        assert_eq!(str_ref, "Hi");
    }

    #[test]
    fn test_buffer_manual_reference() {
        let arena = Arena::new();
        let mut buffer = Buffer::new(0);
        let mut builder = buffer.get_builder();
        assert_eq!(builder.buffer.0.len(), 0);
        builder.push_char('H');
        builder.push_char('i');
        builder.push_char(''); // This is a multi-byte character.
        assert_eq!(builder.buffer.0.len(), 5);
        let str_ref = builder.finish(&arena);
        assert_eq!(str_ref.len(), 5);
        assert_eq!(str_ref, "Hi↩");
    }

    struct CycleParticipant<'a> {
        val: i32,
        next: Option<&'a mut CycleParticipant<'a>>,
    }

    #[test]
    fn test_arena_with_cycle() {
        let arena = DroplessArena::default();

        let a = arena.alloc(CycleParticipant { val: 1, next: None });
        let b = arena.alloc(CycleParticipant { val: 2, next: None });
        a.next = Some(b);
        let c = arena.alloc(CycleParticipant { val: 3, next: None });
        a.next.as_mut().unwrap().next = Some(c);

        // for (i, node) in arena.iter_mut().enumerate() {
        //     match i {
        //         0 => assert_eq!(node.val, 1),
        //         1 => assert_eq!(node.val, 2),
        //         2 => assert_eq!(node.val, 3),
        //         _ => panic!("Too many nodes"),
        //     }
        // }

        assert_eq!(a.val, 1);
        assert_eq!(a.next.as_ref().unwrap().val, 2);
        assert_eq!(a.next.as_ref().unwrap().next.as_ref().unwrap().val, 3);
    }
}