1
//! Double-quoted and triple-quoted string literals.
2

            
3
use winnow::combinator::{alt, cut_err};
4
use winnow::error::{AddContext, ContextError, ErrMode, ModalResult, StrContext, StrContextValue};
5
use winnow::prelude::*;
6
use winnow::token::{any, take_till};
7

            
8
use crate::ast::Expr;
9

            
10
8547839
pub(super) fn parse_string(input: &mut &str) -> ModalResult<Expr> {
11
8547839
    alt((parse_triple_quoted_string, parse_double_quoted_string)).parse_next(input)
12
8547839
}
13

            
14
8547092
fn parse_double_quoted_string(input: &mut &str) -> ModalResult<Expr> {
15
8547092
    let _ = '"'.parse_next(input)?;
16
145751
    let mut result = String::new();
17

            
18
    loop {
19
4021038
        let chunk: &str = take_till(0.., |c| c == '"' || c == '\\').parse_next(input)?;
20
229250
        result.push_str(chunk);
21

            
22
229250
        match cut_err(any)
23
229250
            .context(StrContext::Label("string"))
24
229250
            .context(StrContext::Expected(StrContextValue::Description(
25
229250
                "closing quote",
26
229250
            )))
27
229250
            .parse_next(input)?
28
        {
29
145668
            '"' => return Ok(Expr::String(result)),
30
            '\\' => {
31
83499
                let escaped = any.parse_next(input)?;
32
83499
                match escaped {
33
665
                    'n' => result.push('\n'),
34
581
                    't' => result.push('\t'),
35
83
                    'r' => result.push('\r'),
36
1577
                    '\\' => result.push('\\'),
37
80593
                    '"' => result.push('"'),
38
                    c => {
39
                        result.push('\\');
40
                        result.push(c);
41
                    }
42
                }
43
            }
44
            _ => unreachable!(),
45
        }
46
    }
47
8547092
}
48

            
49
8547839
fn parse_triple_quoted_string(input: &mut &str) -> ModalResult<Expr> {
50
8547839
    let _ = "\"\"\"".parse_next(input)?;
51
747
    let mut content = String::new();
52

            
53
    loop {
54
47642
        if input.starts_with("\"\"\"") {
55
747
            let _ = "\"\"\"".parse_next(input)?;
56
747
            return Ok(Expr::String(content));
57
46895
        }
58
46895
        if input.is_empty() {
59
            return Err(ErrMode::Cut(
60
                ContextError::new()
61
                    .add_context(input, &input.checkpoint(), StrContext::Label("string"))
62
                    .add_context(
63
                        input,
64
                        &input.checkpoint(),
65
                        StrContext::Expected(StrContextValue::Description("closing \"\"\"")),
66
                    ),
67
            ));
68
46895
        }
69
46895
        content.push(any.parse_next(input)?);
70
    }
71
8547839
}