mdplayscript/
speech.rs

1use std::collections::VecDeque;
2use pulldown_cmark::{Event, CowStr};
3use crate::{find_one_of, find_puncts_end};
4use crate::parser::split_speech_heading;
5
6#[derive(Debug,Clone,PartialEq)]
7pub struct Speech<'a> {
8    pub heading: Heading<'a>,
9    pub body: Vec<Inline<'a>>,
10}
11
12#[derive(Debug,Clone,PartialEq)]
13pub struct Heading<'a> {
14    pub character: CowStr<'a>,
15    pub direction: Direction<'a>,
16}
17
18#[derive(Debug,Clone,PartialEq)]
19pub enum Inline<'a> {
20    Event(Event<'a>),
21    Direction(Direction<'a>),
22}
23
24#[derive(Debug,Clone,PartialEq)]
25pub struct Direction<'a>(pub Vec<Event<'a>>);
26
27impl<'a> Direction<'a> {
28    pub fn new() -> Self {
29        Self(Vec::new())
30    }
31
32    pub fn push_string(&mut self, s: String) {
33        self.0.push(Event::Text(s.into()));
34    }
35}
36
37pub fn parse_speech<'a>(events: Vec<Event<'a>>) -> Result<Speech<'a>, Vec<Event<'a>>> {
38    let (heading, first) = match events.first() {
39        Some(Event::Text(s)) => {
40            let s = s.to_string();
41            if let Some((heading, line)) = split_speech_heading(s.as_ref()) {
42                let heading = heading.to_owned();
43                let line = line.to_owned();
44                (parse_heading(&heading), Event::Text(line.into()))
45            } else {
46                return Err(events);
47            }
48        },
49        _ => return Err(events),
50    };
51
52    let mut speech = vec![first];
53    for e in events.into_iter().skip(1) {
54        speech.push(e);
55    }
56    remove_trailing_softbreak(&mut speech);
57
58    let body = parse_body(speech);
59
60    Ok(Speech {
61        heading: heading,
62        body: body,
63    })
64}
65
66pub fn parse_heading(s: &str) -> Heading<'static> {
67    let open_paren = match s.find('(') {
68        Some(pos) => pos,
69        None => {
70            let character = s.trim().to_owned();
71            return Heading {
72                character: character.into(),
73                direction: Direction::new(),
74            };
75        },
76    };
77
78    let character = s[..open_paren].trim().to_owned();
79    let s = &s[open_paren+1..];
80    let mut close_paren = s.len();
81    for (index, c) in s.char_indices() {
82        if c == ')' {
83            close_paren = index;
84            break;
85        }
86    }
87
88    let s = s[..close_paren].to_owned();
89    let mut direction = Direction::new();
90    direction.push_string(s);
91
92    Heading {
93        character: character.into(),
94        direction: direction,
95    }
96}
97
98pub fn parse_body<'a>(events: Vec<Event<'a>>) -> Vec<Inline<'a>> {
99    let mut body = Vec::new();
100    let mut direction = Vec::new();
101    let mut paren_level = 0usize;
102
103    for event in ParenSplitter::new(events.into_iter()) {
104        match event {
105            Event::Text(s) if s.as_ref() == "(" => {
106                if paren_level > 0 {
107                    direction.push(Event::Text(s));
108                }
109
110                paren_level = paren_level + 1;
111            },
112            Event::Text(s) if s.as_ref() == ")" => {
113                match paren_level {
114                    0 => {
115                        body.push(Inline::Event(Event::Text(s)));
116                    },
117                    1 => {
118                        let mut pushed = Vec::new();
119                        std::mem::swap(&mut pushed, &mut direction);
120                        let pushed = Direction(pushed);
121                        body.push(Inline::Direction(pushed));
122                        paren_level = paren_level - 1;
123                    },
124                    _ => {
125                        direction.push(Event::Text(s));
126                        paren_level = paren_level -1;
127                    },
128                }
129            },
130            _ => {
131                if paren_level > 0 {
132                    direction.push(event);
133                } else {
134                    body.push(Inline::Event(event));
135                }
136            },
137        }
138    }
139
140    if direction.len() > 0 {
141        let direction = Direction(direction);
142        body.push(Inline::Direction(direction));
143    }
144
145    trim_start_of_line_head(body)
146}
147
148#[derive(Debug)]
149pub struct ParenSplitter<'a, I> {
150    iter: I,
151    queue: VecDeque<Event<'a>>,
152}
153
154impl<'a, I> ParenSplitter<'a, I>
155where
156    I: Iterator<Item=Event<'a>>,
157{
158    pub fn new(iter: I) -> Self {
159        Self {
160            iter: iter,
161            queue: VecDeque::new(),
162        }
163    }
164}
165
166impl<'a, I> Iterator for ParenSplitter<'a, I>
167where
168    I: Iterator<Item=Event<'a>>,
169{
170    type Item = Event<'a>;
171
172    fn next(&mut self) -> Option<Self::Item> {
173        if let Some(event) = self.queue.pop_front() {
174            return Some(event);
175        }
176
177        match self.iter.next() {
178            Some(Event::Text(s)) => {
179                for text in split_at_paren(s).into_iter() {
180                    self.queue.push_back(Event::Text(text.into()));
181                }
182            },
183            item => return item,
184        }
185
186        self.queue.pop_front()
187    }
188}
189
190fn split_at_paren<T: AsRef<str>>(s: T) -> Vec<String> {
191    let mut s = s.as_ref();
192    let mut v = Vec::new();
193
194    loop {
195        if s.len() == 0 {
196            break;
197        }
198
199        match find_one_of(s, "()") {
200            Some((index, c)) => {
201                let before = &s[..index];
202                let (parens, after) = find_puncts_end(&s[index..], c);
203                v.push(before.to_owned());
204                v.push(parens.to_owned());
205                s = after;
206            },
207            None => {
208                v.push(s.to_owned());
209                s = "";
210            },
211        }
212    }
213
214    v
215}
216
217pub fn trim_start_of_line_head<'a>(body: Vec<Inline<'a>>) -> Vec<Inline<'a>> {
218    let mut ret = Vec::with_capacity(body.len());
219    let mut is_line_head = true;
220
221    for inline in body.into_iter() {
222        match (inline, is_line_head) {
223            (Inline::Event(Event::Text(s)), true) => {
224                let trimmed = s.trim_start();
225                if trimmed.len() > 0 {
226                    let trimmed = trimmed.to_owned();
227                    ret.push(Inline::Event(Event::Text(trimmed.into())));
228                }
229                is_line_head = false;
230            },
231            (inline @ Inline::Event(Event::SoftBreak), _) => {
232                ret.push(inline);
233                is_line_head = true;
234            },
235            (inline, _) => {
236                ret.push(inline);
237                is_line_head = false;
238            },
239        }
240    }
241
242    ret
243}
244
245fn remove_trailing_softbreak<'a>(events: &mut Vec<Event<'a>>) {
246    match events.pop() {
247        Some(Event::SoftBreak) => {},
248        Some(e) => {
249            events.push(e);
250        },
251        None => {},
252    }
253}
254
255#[cfg(test)]
256mod test {
257    use super::*;
258    use pulldown_cmark::Event;
259    use big_s::S;
260
261    #[test]
262    fn parse_heading_only_with_character() {
263        assert_eq!(parse_heading("A  "), Heading {
264            character: "A".into(),
265            direction: Direction::new(),
266        });
267    }
268
269    #[test]
270    fn parse_heading_with_direction() {
271        assert_eq!(parse_heading("A (running) "), Heading {
272            character: "A".into(),
273            direction: Direction(vec![Event::Text("running".into())]),
274        });
275    }
276
277    #[test]
278    fn split_parens_in_direction() {
279        assert_eq!(split_at_paren("A (running)"), vec![S("A "), S("("), S("running"), S(")")]);
280        assert_eq!(split_at_paren("xx (dd) yy"), vec![S("xx "), S("("), S("dd"), S(")"), S(" yy")]);
281        assert_eq!(split_at_paren("Escaped (( example"), vec![S("Escaped "), S("(("), S(" example")]);
282    }
283
284    #[test]
285    fn paren_splitter_for_two_lines() {
286        let v = vec![Event::Text("Hello! (xxx)".into()), Event::SoftBreak, Event::Text("Bye!".into())];
287        let mut iter = ParenSplitter::new(v.into_iter());
288
289        assert_eq!(iter.next(), Some(Event::Text("Hello! ".into())));
290        assert_eq!(iter.next(), Some(Event::Text("(".into())));
291        assert_eq!(iter.next(), Some(Event::Text("xxx".into())));
292        assert_eq!(iter.next(), Some(Event::Text(")".into())));
293        assert_eq!(iter.next(), Some(Event::SoftBreak));
294        assert_eq!(iter.next(), Some(Event::Text("Bye!".into())));
295        assert_eq!(iter.next(), None);
296    }
297
298    #[test]
299    fn parse_body_only_with_text() {
300        let v = vec![Event::Text("Hello!".into()), Event::SoftBreak];
301        assert_eq!(parse_body(v), vec![Inline::Event(Event::Text("Hello!".into())), Inline::Event(Event::SoftBreak)]);
302    }
303
304    #[test]
305    fn parse_body_with_direction() {
306        let input = vec![
307            Event::Text("Hello! (running) Bye!".into()),
308        ];
309        let output = vec![
310            Inline::Event(Event::Text("Hello! ".into())),
311            Inline::Direction(Direction(
312                    vec![Event::Text("running".into())]
313            )),
314            Inline::Event(Event::Text(" Bye!".into())),
315        ];
316        assert_eq!(parse_body(input), output);
317    }
318
319    #[test]
320    fn parse_body_with_nested_parens() {
321        let input = vec![
322            Event::Text("Hello! (running (xxx) ) Bye!".into()),
323        ];
324        let output = vec![
325            Inline::Event(Event::Text("Hello! ".into())),
326            Inline::Direction(Direction(vec![
327                    Event::Text("running ".into()),
328                    Event::Text("(".into()),
329                    Event::Text("xxx".into()),
330                    Event::Text(")".into()),
331                    Event::Text(" ".into()),
332            ])),
333            Inline::Event(Event::Text(" Bye!".into())),
334        ];
335        assert_eq!(parse_body(input), output);
336    }
337
338    #[test]
339    fn parse_speech_of_one_line() {
340        let input = vec![
341            Event::Text("A (running)> Hello! (exit)".into()),
342        ];
343        let output = Speech {
344            heading: Heading {
345                character: "A".into(),
346                direction: Direction(vec![Event::Text("running".into())]),
347            },
348            body: vec![
349                Inline::Event(Event::Text("Hello! ".into())),
350                Inline::Direction(Direction(vec![
351                        Event::Text("exit".into()),
352                ])),
353            ],
354        };
355        assert_eq!(parse_speech(input), Ok(output));
356    }
357
358    #[test]
359    fn trim_start_of_body_line_head() {
360        let input = vec![
361            Inline::Event(Event::Text(" Hello!".into())),
362            Inline::Event(Event::SoftBreak),
363            Inline::Event(Event::Text("   Ah!".into())),
364            Inline::Event(Event::SoftBreak),
365            Inline::Event(Event::Text(" Oh!".into())),
366            Inline::Direction(Direction(vec![Event::Text("exit".into())])),
367            Inline::Event(Event::Text(" zzz".into())),
368        ];
369        let output = vec![
370            Inline::Event(Event::Text("Hello!".into())),
371            Inline::Event(Event::SoftBreak),
372            Inline::Event(Event::Text("Ah!".into())),
373            Inline::Event(Event::SoftBreak),
374            Inline::Event(Event::Text("Oh!".into())),
375            Inline::Direction(Direction(vec![Event::Text("exit".into())])),
376            Inline::Event(Event::Text(" zzz".into())),
377        ];
378        assert_eq!(trim_start_of_line_head(input), output);
379    }
380}