dialogue_rs/script/
block.rs1use crate::script::{
30 line::Line,
31 parser::{Parser, Rule},
32 TopLevelElement,
33};
34use anyhow::bail;
35use pest::{iterators::Pair, Parser as PestParser};
36use std::fmt;
37
38#[derive(Debug, PartialEq, Eq, Clone)]
40pub struct Block {
41 inner: Vec<TopLevelElement>,
42}
43
44impl fmt::Display for Block {
45 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46 self.fmt_with_indent(f, 1)
47 }
48}
49
50impl Block {
51 pub fn new(inner: Vec<TopLevelElement>) -> Self {
53 Self { inner }
54 }
55
56 pub fn empty() -> Self {
58 Self::new(Vec::new())
59 }
60
61 pub fn elements(&self) -> &[TopLevelElement] {
63 &self.inner
64 }
65
66 pub fn parse(block_str: &str) -> Result<Self, anyhow::Error> {
68 let mut pairs = Parser::parse(Rule::Block, block_str)?;
69 let pair = pairs.next().expect("a pair exists");
70 assert_eq!(
71 pairs.next(),
72 None,
73 "parsing a block should only ever return one block"
74 );
75
76 pair.try_into()
77 }
78
79 pub fn fmt_with_indent(&self, f: &mut fmt::Formatter<'_>, indent: usize) -> fmt::Result {
81 for el in &self.inner {
82 match el {
83 TopLevelElement::Block(block) => block.fmt_with_indent(f, indent + 1)?,
84 TopLevelElement::Line(line) => {
85 for _ in 0..indent {
86 write!(f, " ")?;
87 }
88 write!(f, "{line}")?;
89 }
90 TopLevelElement::Comment(comment) => {
91 for _ in 0..indent {
92 write!(f, " ")?;
93 }
94 write!(f, "{comment}")?;
95 }
96 }
97 }
98
99 Ok(())
100 }
101}
102
103impl TryFrom<Pair<'_, Rule>> for Block {
104 type Error = anyhow::Error;
105
106 fn try_from(pair: Pair<'_, Rule>) -> Result<Self, Self::Error> {
107 match pair.as_rule() {
108 Rule::Block => {
109 let inner = pair
110 .into_inner()
111 .map(|pair| match pair.as_rule() {
112 Rule::Block => Block::try_from(pair).map(TopLevelElement::Block),
113 Rule::Line => Line::try_from(pair).map(TopLevelElement::Line),
114 _ => unreachable!(
115 "Blocks can't contain anything other than inner blocks or lines"
116 ),
117 })
118 .collect::<Result<Vec<_>, _>>()?;
119
120 Ok(Self { inner })
121 }
122 _ => bail!("Pair is not a block: {:#?}", pair),
123 }
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::Block;
130 use crate::script::{command::Command, line::Line};
131 use pretty_assertions::assert_eq;
132
133 #[test]
134 fn test_single_line_parses_correctly() {
135 let input = " |CHOICE| do the thing\n";
136 let expected = Block {
137 inner: vec![Line::command(Command::parse("|CHOICE| do the thing").unwrap()).into()],
138 };
139 let actual = Block::parse(input).expect("block is valid");
140 assert_eq!(expected, actual);
141 }
142
143 #[test]
144 fn test_multiple_lines_parse_correctly_1() {
145 let input = " |CHOICE| do the thing
146 |CHOICE| do the other thing
147 |CHOICE| do the third thing
148";
149 let expected = Block {
150 inner: vec![
151 Line::command(Command::parse("|CHOICE| do the thing").unwrap()).into(),
152 Line::command(Command::parse("|CHOICE| do the other thing").unwrap()).into(),
153 Line::command(Command::parse("|CHOICE| do the third thing").unwrap()).into(),
154 ],
155 };
156 let actual = Block::parse(input).expect("block is valid");
157 assert_eq!(expected, actual);
158 }
159
160 #[test]
161 fn test_multiple_lines_parse_correctly_2() {
162 let input = " |TEST| A1
163 |TEST| B1
164 |TEST| B2
165 |TEST| B3
166";
167 let expected = Block {
168 inner: vec![
169 Line::command(Command::parse("|TEST| A1").unwrap()).into(),
170 Block {
171 inner: vec![
172 Line::command(Command::parse("|TEST| B1").unwrap()).into(),
173 Line::command(Command::parse("|TEST| B2").unwrap()).into(),
174 Line::command(Command::parse("|TEST| B3").unwrap()).into(),
175 ],
176 }
177 .into(),
178 ],
179 };
180 let actual = Block::parse(input).expect("block is valid");
181 assert_eq!(expected, actual);
182 }
183
184 #[test]
185 fn test_nested_blocks_parse_correctly() {
186 let input = " |SAY| First level
187 |SAY| Second level
188 |SAY| Third level
189";
190 let expected = Block::new(vec![
191 Command::new("SAY", None, Some("First level")).into(),
192 Block::new(vec![
193 Command::new("SAY", None, Some("Second level")).into(),
194 Block::new(vec![Command::new("SAY", None, Some("Third level")).into()]).into(),
195 ])
196 .into(),
197 ]);
198
199 let actual = Block::parse(input).expect("block is valid");
200
201 assert_eq!(expected, actual);
202 }
203
204 #[test]
205 fn test_one_line_round_trip() {
206 let input = " |CHOICE| do the thing\n";
207 let block = Block::parse(input).expect("block is valid");
208 let output = block.to_string();
209
210 assert_eq!(input, output);
211 }
212
213 #[test]
214 fn test_multiline_round_trip_1() {
215 let input = " |CHOICE| do the thing
216 |CHOICE| do the other thing
217 |CHOICE| do the third thing
218";
219 let block = Block::parse(input).expect("block is valid");
220 let output = block.to_string();
221
222 assert_eq!(input, output);
223 }
224
225 #[test]
226 fn test_multiline_round_trip_2() {
227 let input = " |TEST| A1
228 |TEST| B1
229 |TEST| B2
230 |TEST| B3
231";
232 let block = Block::parse(input).unwrap();
233 print!("{block:#?}");
234
235 let output = block.to_string();
236
237 assert_eq!(input, output);
238 }
239
240 #[test]
241 fn test_multiline_round_trip_3() {
242 let input = " |SAY| First level
243 |SAY| Second level
244 |SAY| Third level
245";
246 let block = Block::parse(input).expect("block is valid");
247 let output = block.to_string();
248
249 assert_eq!(input, output);
250 }
251}