summaryrefslogtreecommitdiffstats
path: root/src/poem/read/parse.rs
blob: 0b6c5a3527e6f27bdce3a8f45ff183e003f6fcf6 (plain)
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
129
130
131
132
133
134
135
136
137
138
139
140
141
use crate::poem::elements::rune::Rune;
use std::str::Chars;

/// Look ahead one character in the input
///
/// May need to look ahead one character in the input string to determine the
/// proper rune. For instance `&`, vs `&&`.
///
/// # Arguments:
/// `chars` - An iterator over the characters in the poem
/// `i` - A counter to keep track of the current column
/// `otherwise` - The rune to return if there are no matches
/// `ahead` - A list of tuples, containing characters to look ahead for,
///           alongside which rune they correspond to
///
/// # Examples:
/// ```
/// next(&mut chars, &mut i, Rune::Write, vec![(">", Rune::Addendum)])
/// next(&mut chars, &mut i, Rune::Quiet, vec![("&", Rune::And)])
/// ```
pub fn next(chars: &mut Chars, i: &mut usize, otherwise: Rune, ahead: Vec<(&str, Rune)>) -> Rune {
    // Initialize rune (the return value) with the default
    let mut rune = otherwise;

    // We need to peek the iterator
    let mut peekable = chars.clone().peekable();

    // Help keep track of matched characters
    let mut j = 0;

    // For each tuple pair (string, rune)...
    for (s, r) in ahead.iter() {
        // For each character in in the string, starting at length j...
        for c in s[j..].chars().into_iter() {
            // If the next char matches...
            match peekable.peek() {
                Some(next) if next == &c => {
                    // Increment counters
                    chars.next();
                    peekable.next();
                    *i += 1;
                    j += 1;

                    // Only update the rune if j equals the length of the string
                    if j == s.len() {
                        rune = *r;
                    }
                }
                Some(_) => {}
                None => {}
            }
        }
    }

    // Return whatever the rune was determined to be
    rune
}

/// Keep pushing to the [Word][super::super::elements::word::Word] stack
///
/// If a [Rune::String][super::super::elements::rune::Rune] character is found,
/// stop interpreting special characters, and push all characters to the
/// [Word][super::super::elements::word::Word] stack, until the corresponding
/// [Rune::String][super::super::elements::rune::Rune] character is found.
#[macro_export]
macro_rules! string {
    ($chars:expr, $j:expr, $i:expr, $c:expr, $word:expr) => {
        let token = $c;
        loop {
            match $chars.next() {
                None => return Err(Mishap::PartialMishap($j, $i, $c)),
                Some(c) if c == token => break,
                Some(c) if token == '\'' && c == '$' => {
                    $word.push('\x0e');
                    $i += 1;
                }
                Some(c) if c == '\\' => {
                    let c = match $chars.next() {
                        Some(c) => c,
                        None => continue,
                    };
                    $word.push(c);
                    $i += 1;
                }
                Some(c) => {
                    $word.push(c);
                    $i += 1;
                }
            }
        }
    };
}

/// Same as [string!] macro, but look for newline or EOF
#[macro_export]
macro_rules! remark {
    ($chars:expr) => {
        loop {
            match $chars.next() {
                None => break,
                Some(c) if c == '\n' => break,
                Some(_) => {}
            }
        }
        continue;
    };
}

/// Same as the [string!] macro, but don't `continue`
#[macro_export]
macro_rules! poem {
    ($chars:expr, $j:expr, $i:expr, $c:expr, $verse:expr, $word:expr, $env:expr) => {
        let token = $c;
        let mut poetry = Word::new();
        loop {
            match $chars.next() {
                None => return Err(Mishap::PartialMishap($j, $i, $c)),
                Some(c) if c == token => break,
                Some(c) if c == '\\' => {
                    let c = match $chars.next() {
                        Some(c) => c,
                        None => continue,
                    };
                    $word.push(c);
                    $i += 1;
                }
                Some(c) => {
                    poetry.push(c);
                    $i += 1;
                }
            }
        }
        let sp = Poem::read(poetry.iter().collect(), $env);
        let sp = match sp {
            Ok(sp) => sp,
            Err(e) => return Err(e),
        };
        $verse.poems.push(sp);
        $word.push('\x0b');
    };
}