111 lines
3.1 KiB
Rust
111 lines
3.1 KiB
Rust
use crate::{
|
|
representation::{Representation, EBNF},
|
|
ParseResult, Parser, ParserExtension,
|
|
};
|
|
|
|
pub fn literal<'a>(expected: &'static str) -> impl Parser<&'a str, &'a str, ()> {
|
|
let p = move |input: &'a str| match input.get(0..expected.len()) {
|
|
Some(next) if next == expected => Ok((next, &input[expected.len()..])),
|
|
_ => Err(((), input)),
|
|
};
|
|
let production = EBNF::StringTerminal(expected.into());
|
|
p.to_anno()
|
|
.with_repr(Representation::new().with_production(production))
|
|
}
|
|
|
|
pub fn literal_char<'a>(expected: char) -> impl Parser<&'a str, char, ()> {
|
|
(move |input: &'a str| match input.chars().next() {
|
|
Some(ch) if ch == expected => Ok((expected, &input[ch.len_utf8()..])),
|
|
_ => Err(((), input)),
|
|
})
|
|
.to_anno()
|
|
.with_repr(Representation::new().with_production(EBNF::CharTerminal(expected)))
|
|
}
|
|
|
|
pub fn one_of<'a>(items: &'static str) -> impl Parser<&'a str, char, ()> {
|
|
(move |input: &'a str| {
|
|
if let Some(ch) = input.chars().next() {
|
|
if items.contains(ch) {
|
|
let (_first, rest) = input.split_at(1);
|
|
return Ok((ch, rest));
|
|
}
|
|
}
|
|
Err(((), input))
|
|
})
|
|
.to_anno()
|
|
.with_repr(
|
|
Representation::new().with_production(EBNF::Alternation(
|
|
items
|
|
.chars()
|
|
.map(|ch| EBNF::CharTerminal(ch))
|
|
.collect::<Vec<_>>(),
|
|
)),
|
|
)
|
|
}
|
|
|
|
/// Parses a standard identifier in a programming language
|
|
pub fn identifier(input: &str) -> ParseResult<&str, String, ()> {
|
|
let mut chars = input.chars();
|
|
let mut buf = String::new();
|
|
|
|
match chars.next() {
|
|
Some(ch) if ch.is_alphabetic() => buf.push(ch),
|
|
_ => return Err(((), input)),
|
|
}
|
|
|
|
for next in chars {
|
|
if next.is_alphanumeric() {
|
|
buf.push(next);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
let next_index = buf.len();
|
|
Ok((buf, &input[next_index..]))
|
|
}
|
|
|
|
pub struct Whitespace;
|
|
|
|
impl Parser<&str, char, ()> for Whitespace {
|
|
fn name(&self) -> Option<String> {
|
|
Some("whitespace".into())
|
|
}
|
|
|
|
fn representation(&self) -> Representation {
|
|
Representation::new().with_production(EBNF::LabeledTerminal("whitespace".into()))
|
|
}
|
|
|
|
fn parse<'a>(&self, input: &'a str) -> ParseResult<&'a str, char, ()> {
|
|
match input.chars().next() {
|
|
Some(ch) if ch.is_whitespace() => Ok((ch, &input[1..])),
|
|
_ => Err(((), input)),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn primitive_parsers() {
|
|
let parser = literal_char('f');
|
|
assert_eq!(Ok(('f', "unky")), parser.parse("funky"));
|
|
|
|
let repr = parser.representation();
|
|
assert!(matches!(repr.production(), EBNF::CharTerminal('f')));
|
|
|
|
let parser = one_of("asdf");
|
|
let production = parser.representation().production();
|
|
assert!(
|
|
matches!(production, EBNF::Alternation(v) if matches!(v.as_slice(), [
|
|
EBNF::CharTerminal('a'),
|
|
EBNF::CharTerminal('s'),
|
|
EBNF::CharTerminal('d'),
|
|
EBNF::CharTerminal('f'),
|
|
]))
|
|
);
|
|
}
|
|
}
|