welly_parser/
word.rs

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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
//! Welly's words, including keywords.

use std::collections::{HashMap};
use std::{fmt};

use super::{Tree, Stream, Context, Parse};

/// Represents a contiguous string of ASCII whitespace characters.
///
/// The characters can only be retrieved if you know the `Whitespace`'s
/// [`Location`].
///
/// [`Location`]: super::Location
#[derive(Debug, Clone, PartialEq)]
pub struct Whitespace;

impl Tree for Whitespace {}

/// Represents a contiguous string of ASCII symbol characters.
///
/// Symbol characters are those which appear in Welly's arithmetic operators.
/// Brackets, commas and semicolons are not considered to by symbol characters.
#[derive(Debug, Clone, PartialEq)]
pub struct Symbol(pub String);

impl Tree for Symbol {}

/// Represents a contiguous string of ASCII alpha-numeric characters.
///
/// Underscore is considered to be alpha-numeric. Note that this type can
/// represent a decimal integer.
#[derive(Debug, Clone, PartialEq)]
pub struct Alphanumeric(pub String);

impl Tree for Alphanumeric {}

// ----------------------------------------------------------------------------

/// Three classes of character combine with similar neighbours to make a word.
#[derive(Debug, Copy, Clone, PartialEq)]
enum CharacterClass {
    /// A whitespace character.
    WHITESPACE,

    /// A character that can appear in a multi-character operator.
    SYMBOL,

    /// An ASCII letter, digit or underscore.
    ALPHANUMERIC,
}

impl CharacterClass {
    /// Map `s` to a `Self`, if possible.
    fn classify(c: char) -> Option<Self> {
        use CharacterClass::*;
        match c {
            '\t' | '\n' | '\r' | ' ' =>
                Some(WHITESPACE),
            '!' | '$' | '%' | '^' | '&' | '*' | '-' | '+' | '=' | ':' | '@' | '~' | '<' | '>' | '?' | '.' | '/' =>
                Some(SYMBOL),
            '0'..='9' | 'A'..='Z' | 'a'..='z' | '_' =>
                Some(ALPHANUMERIC),
            _ => None,
        }
    }

    /// Combine `self` with `s` to make a [`dyn Tree`].
    fn wrap(self, s: String) -> Box<dyn Tree> {
        use CharacterClass::*;
        match self {
            WHITESPACE => Box::new(Whitespace),
            SYMBOL => Box::new(Symbol(s)),
            ALPHANUMERIC => Box::new(Alphanumeric(s)),
        }
    }
}

// ----------------------------------------------------------------------------

/// A [`Parse`] implementation that recognises [`Whitespace`]s, [`Symbol`]s and
/// [`Alphanumeric`]s.
///
/// It parses a [`Stream`] that contains [`char`]s.
#[derive(Default)]
pub struct Parser(HashMap<&'static str, Box<dyn Fn() -> Box<dyn Tree>>>);

impl Parser {
    pub fn add_keywords<T: Tree + Clone>(&mut self) {
        T::declare_keywords(|name, tree| {
            let old = self.0.insert(name, Box::new(move || Box::new(tree.clone())));
            assert!(old.is_none(), "Keyword '{}' has multiple meanings", name);
        });
    }
}

impl fmt::Debug for Parser {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_tuple("Parser")
            .field(&Vec::from_iter(self.0.keys().copied()))
            .finish()
    }
}

impl<'a> Parse for &'a Parser {
    fn parse(
        &self,
        input: &mut Context<impl Stream>,
    ) -> Result<Box<dyn Tree>, String> {
        if let Some(c) = input.read::<char>()? {
            if let Some(cc) = CharacterClass::classify(*c) {
                let mut s = String::new();
                s.push(*c);
                while let Some(c) = input.read_if(
                    |&c| CharacterClass::classify(c) == Some(cc)
                )? {
                    s.push(*c);
                }
                Ok(if let Some(f) = self.0.get(&s.as_ref()) {
                    f()
                } else {
                    s.shrink_to_fit();
                    cc.wrap(s)
                })
            } else {
                Ok(c)
            }
        } else {
            input.read_any()
        }
    }
}

impl Parse for Parser {
    fn parse(
        &self,
        input: &mut Context<impl Stream>,
    ) -> Result<Box<dyn Tree>, String> {
        (&self).parse(input)
    }
}

// ----------------------------------------------------------------------------

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{Characters};

    /// A minimal mock-up of some Welly keywords.
    #[derive(Debug, Copy, Clone, PartialEq)]
    enum Keyword {RETURN, EQUALS}
    use Keyword::*;

    impl Tree for Keyword {
        fn declare_keywords(mut declare: impl FnMut(&'static str, Self)) {
            declare("return", RETURN);
            declare("==", EQUALS);
        }
    }

    #[test]
    fn keywords() {
        let mut parser = Parser::default();
        parser.add_keywords::<Keyword>();
        let mut stream = parser.parse_stream(Characters::new("return foo==69;", true));
        assert_eq!(stream.read(), RETURN);
        assert_eq!(stream.read(), Whitespace);
        assert_eq!(stream.read(), Alphanumeric("foo".into()));
        assert_eq!(stream.read(), EQUALS);
        assert_eq!(stream.read(), Alphanumeric("69".into()));
        assert_eq!(stream.read(), ';');
        assert_eq!(stream.read(), crate::EndOfFile);
    }
}