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);
}
}