1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
//! Types used by backends to produce pretty output.
use crate::Error;
use std::io::Write;

/// You might not realize how hard it can be to type exactly 4 spaces.
pub const FOUR_SPACES: &str = "    ";

/// Convenience helper to allow backends to write code with indentation.
pub struct IndentWriter<'a> {
    one_indent: String,
    current_level: usize,
    writer: &'a mut dyn Write,
}

impl<'a> IndentWriter<'a> {
    pub fn indent(&mut self) {
        self.current_level += 1;
    }

    pub fn unindent(&mut self) {
        if self.current_level == 0 {
            panic!("Tried to un-indent past start of line.")
        }

        self.current_level -= 1;
    }

    pub fn new(writer: &'a mut dyn Write) -> Self {
        Self {
            one_indent: FOUR_SPACES.to_string(),
            current_level: 0,
            writer,
        }
    }

    pub fn with_indent(writer: &'a mut dyn Write, one_indent: &str) -> Self {
        Self {
            one_indent: one_indent.to_string(),
            current_level: 0,
            writer,
        }
    }

    pub fn indented(&mut self, f: impl FnOnce(&mut dyn Write) -> std::io::Result<()>) -> Result<(), Error> {
        for _ in 0..self.current_level {
            write!(&mut self.writer, "{}", self.one_indent)?;
        }

        f(&mut self.writer)?;

        Ok(())
    }

    pub fn indented_block(&mut self, block: Option<(&str, &str)>, f: impl FnOnce(&mut Self) -> Result<(), Error>) -> Result<(), Error> {
        if let Some(block) = block {
            self.indented(|w| writeln!(w, "{}", block.0))?;
        }
        self.indent();

        f(self)?;

        self.unindent();

        if let Some(block) = block {
            self.indented(|w| writeln!(w, "{}", block.1))?;
        }

        Ok(())
    }

    pub fn writer(&mut self) -> &mut dyn Write {
        &mut self.writer
    }

    pub fn newline(&mut self) -> Result<(), Error> {
        writeln!(&mut self.writer)?;
        Ok(())
    }
}

/// Writes a line of code, possibly with multiple indentations. Used in backends.
#[macro_export]
macro_rules! indented {
    ($w:expr, [ $($i:pat)+ ], $x:expr, $($param:expr),*) => {
        {
            $(
                let $i = ();
                $w.indent();
            )*
            let rval = $w.indented(|w| writeln!(w, $x, $($param),*));
            $(
                let $i = ();
                $w.unindent();
            )*
            rval
        }

    };

    ($w:expr, [ $($i:pat)+ ], $x:expr) => {
        {
            $(
                let $i = ();
                $w.indent();
            )*
            let rval = $w.indented(|w| writeln!(w, $x));
            $(
                let $i = ();
                $w.unindent();
            )*
            rval
        }

    };


    ($w:expr, $x:expr, $($param:expr),*) => {
        {
            $w.indented(|w| writeln!(w, $x, $($param),*))
        }
    };

    ($w:expr, $x:expr) => {
        {
            $w.indented(|w| writeln!(w, $x))
        }
    };
}