quicklatex 0.1.0

A program to help me write LaTeX quickly
Documentation
use std::fmt::Display;

use crate::{
    first_pass,
    misc::{Pos, Span},
};

#[derive(Debug, PartialEq, Eq)]
pub struct Block
{
    pub kind: Vec<char>,
    pub content: Text,
}

#[derive(Debug, PartialEq, Eq)]
pub enum TextPiece
{
    Text(Vec<char>),
    Literal(Vec<char>),
    Block(Block),
    CommandHeader(Vec<char>),
    Braces(Text),
    Comment,
}

#[derive(Debug, PartialEq, Eq)]
pub struct Text
{
    pub pieces: Vec<(TextPiece, Span)>,
}

// Fixing this would make it less clear, not more.
#[allow(clippy::too_many_lines)]
pub fn parse(unparsed: &[(first_pass::TextPiece, Span)]) -> Text
{
    let mut pieces = vec![];

    let mut i = 0;

    while unparsed[i].0 != first_pass::TextPiece::End
    {
        match &unparsed[i].0
        {
            first_pass::TextPiece::Literal(literal) =>
            {
                pieces.push((TextPiece::Literal(literal.clone()), unparsed[i].1));
            }
            first_pass::TextPiece::Text(c) =>
            {
                let mut text = vec![*c];
                let start = unparsed[i].1;

                i += 1;

                while let first_pass::TextPiece::Text(c) = unparsed[i].0
                {
                    text.push(c);
                    i += 1;
                }
                i -= 1;

                pieces.push((
                    TextPiece::Text(text),
                    Span {
                        start: start.start,
                        end: unparsed[i].1.end,
                    },
                ));
            }
            command if command.is_begin() =>
            {
                i += 1;
                if let first_pass::TextPiece::Braces(kind) = &unparsed[i].0
                {
                    let mut counter = 1;
                    let mut inner = vec![];

                    i += 1;

                    while counter > 0
                    {
                        inner.push(unparsed[i].clone());

                        if unparsed[i].0.is_begin()
                        {
                            counter += 1;
                        }
                        else if unparsed[i].0.is_end()
                        {
                            counter -= 1;
                        }
                        i += 1;
                    }

                    inner.pop();
                    inner.extend(vec![
                        (
                            first_pass::TextPiece::End,
                            Span {
                                start: Pos::default(),
                                end: Pos::default()
                            }
                        );
                        10
                    ]);

                    assert!(
                        unparsed[i].0.does_kind_match(kind),
                        "Wrong \\end at  from {}, {} to {}, {}.",
                        unparsed[i].1.start.line,
                        unparsed[i].1.start.column,
                        unparsed[i].1.end.line,
                        unparsed[i].1.end.column,
                    );

                    let span = Span {
                        start: inner[0].1.start,
                        end: inner[inner.len() - 11].1.end,
                    };

                    pieces.push((
                        TextPiece::Block(Block {
                            kind: kind.to_owned(),
                            content: parse(&inner),
                        }),
                        span,
                    ));
                }
                else
                {
                    panic!(
                        "\\begin needs {{…}} afterwards at from {}, {} to {}, {}.",
                        unparsed[i].1.start.line,
                        unparsed[i].1.start.column,
                        unparsed[i].1.end.line,
                        unparsed[i].1.end.column,
                    );
                }
            }
            first_pass::TextPiece::CommandHeader(command) =>
            {
                pieces.push((TextPiece::CommandHeader(command.clone()), unparsed[i].1));
            }
            first_pass::TextPiece::Braces(braces) =>
            {
                let first_pass::Text { pieces: first } = first_pass::parse(braces);

                pieces.push((TextPiece::Braces(parse(&first)), unparsed[i].1));
            }
            first_pass::TextPiece::Comment => pieces.push((TextPiece::Comment, unparsed[i].1)),
            first_pass::TextPiece::End => break,
        }

        i += 1;
    }

    Text { pieces }
}

impl Display for Text
{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
    {
        for piece in &self.pieces
        {
            match &piece.0
            {
                TextPiece::Text(s) | TextPiece::Literal(s) => write!(f, "{}", s.iter().collect::<String>())?,

                TextPiece::Block(Block { kind, content }) => write!(
                    f,
                    "\\begin{{{}}}{}\\end{{{}}}",
                    kind.iter().collect::<String>(),
                    content,
                    kind.iter().collect::<String>()
                )?,
                TextPiece::CommandHeader(s) => write!(f, "\\{}", s.iter().collect::<String>())?,
                TextPiece::Braces(s) => write!(f, "{{{s}}}")?,
                TextPiece::Comment => writeln!(f, "%")?,
            }
        }

        Ok(())
    }
}

#[cfg(test)]
mod tests
{
    use crate::{
        first_pass::TextPiece as FirstTextPiece,
        misc::{Pos, Span},
        second_pass::{parse, Block, Text, TextPiece},
    };

    // Test just often are long, it wouldn't be better to split this
    // into multiple functions.  Also pedantic lint.
    #[allow(clippy::too_many_lines)]
    #[test]
    fn second_pass_test()
    {
        // This is a default span used in tests where spans aren't
        // actually the thing I want to test and so would just take up
        // space.
        let span = Span {
            start: Pos { line: 564, column: 124 },
            end: Pos { line: 234, column: 876 },
        };

        let tests = vec![
            (
                vec![(
                    FirstTextPiece::End,
                    Span {
                        start: Pos { line: 0, column: 0 },
                        end: Pos { line: 0, column: 0 },
                    },
                )],
                Text { pieces: vec![] },
            ),
            (
                vec![
                    (
                        FirstTextPiece::Literal(vec![]),
                        Span {
                            start: Pos { line: 0, column: 0 },
                            end: Pos { line: 0, column: 0 },
                        },
                    ),
                    (
                        FirstTextPiece::End,
                        Span {
                            start: Pos { line: 0, column: 0 },
                            end: Pos { line: 0, column: 0 },
                        },
                    ),
                ],
                Text {
                    pieces: vec![(
                        TextPiece::Literal(vec![]),
                        Span {
                            start: Pos { line: 0, column: 0 },
                            end: Pos { line: 0, column: 0 },
                        },
                    )],
                },
            ),
            (
                vec![
                    (
                        FirstTextPiece::Literal(vec!['a', 'b', 'c', 'ä']),
                        Span {
                            start: Pos { line: 0, column: 0 },
                            end: Pos { line: 0, column: 4 },
                        },
                    ),
                    (
                        FirstTextPiece::End,
                        Span {
                            start: Pos { line: 0, column: 4 },
                            end: Pos { line: 0, column: 4 },
                        },
                    ),
                ],
                Text {
                    pieces: vec![(
                        TextPiece::Literal(vec!['a', 'b', 'c', 'ä']),
                        Span {
                            start: Pos { line: 0, column: 0 },
                            end: Pos { line: 0, column: 4 },
                        },
                    )],
                },
            ),
            (
                vec![
                    (FirstTextPiece::CommandHeader(vec!['b', 'e', 'g', 'i', 'n']), span),
                    (FirstTextPiece::Braces(vec!['a', 'l', 'i', 'g', 'n']), span),
                    (FirstTextPiece::Literal(vec!['a', 'b', 'c', 'ä']), span),
                    (FirstTextPiece::CommandHeader(vec!['e', 'n', 'd']), span),
                    (FirstTextPiece::Braces(vec!['a', 'l', 'i', 'g', 'n']), span),
                    (FirstTextPiece::End, span),
                ],
                Text {
                    pieces: vec![(
                        TextPiece::Block(Block {
                            kind: vec!['a', 'l', 'i', 'g', 'n'],
                            content: Text {
                                pieces: vec![(TextPiece::Literal(vec!['a', 'b', 'c', 'ä']), span)],
                            },
                        }),
                        span,
                    )],
                },
            ),
            (
                vec![
                    (FirstTextPiece::Literal(vec!['Ä', 'Ö', 'Ü']), span),
                    (FirstTextPiece::CommandHeader(vec!['b', 'e', 'g', 'i', 'n']), span),
                    (FirstTextPiece::Braces(vec!['a', 'l', 'i', 'g', 'n']), span),
                    (FirstTextPiece::Literal(vec!['a', 'b', 'c', 'ä']), span),
                    (FirstTextPiece::CommandHeader(vec!['e', 'n', 'd']), span),
                    (FirstTextPiece::Braces(vec!['a', 'l', 'i', 'g', 'n']), span),
                    (FirstTextPiece::End, span),
                ],
                Text {
                    pieces: vec![
                        (TextPiece::Literal(vec!['Ä', 'Ö', 'Ü']), span),
                        (
                            TextPiece::Block(Block {
                                kind: vec!['a', 'l', 'i', 'g', 'n'],
                                content: Text {
                                    pieces: vec![(TextPiece::Literal(vec!['a', 'b', 'c', 'ä']), span)],
                                },
                            }),
                            span,
                        ),
                    ],
                },
            ),
            (
                vec![
                    (FirstTextPiece::Literal(vec!['Ä', 'Ö', 'Ü']), span),
                    (FirstTextPiece::CommandHeader(vec!['b', 'e', 'g', 'i', 'n']), span),
                    (FirstTextPiece::Braces(vec!['a', 'l', 'i', 'g', 'n']), span),
                    (FirstTextPiece::CommandHeader(vec!['n', 'w', 's']), span),
                    (FirstTextPiece::Literal(vec!['a', 'b', 'c', 'ä']), span),
                    (FirstTextPiece::CommandHeader(vec!['e', 'n', 'd']), span),
                    (FirstTextPiece::Braces(vec!['a', 'l', 'i', 'g', 'n']), span),
                    (FirstTextPiece::End, span),
                ],
                Text {
                    pieces: vec![
                        (TextPiece::Literal(vec!['Ä', 'Ö', 'Ü']), span),
                        (
                            TextPiece::Block(Block {
                                kind: vec!['a', 'l', 'i', 'g', 'n'],
                                content: Text {
                                    pieces: vec![
                                        (TextPiece::CommandHeader(vec!['n', 'w', 's']), span),
                                        (TextPiece::Literal(vec!['a', 'b', 'c', 'ä']), span),
                                    ],
                                },
                            }),
                            span,
                        ),
                    ],
                },
            ),
        ];

        for (input, output) in tests
        {
            assert_eq!(parse(&input), output);
        }
    }
}