summaryrefslogtreecommitdiff
path: root/src/nibble
diff options
context:
space:
mode:
authorGreg Brown <gmb60@cam.ac.uk>2020-11-19 13:57:14 +0000
committerGreg Brown <gmb60@cam.ac.uk>2020-11-19 13:57:14 +0000
commite362e83b996f9ba914c88630bec28b703c535dcc (patch)
tree3872159735e5d0fd4a28b03937ed616a32008f61 /src/nibble
parent7c79f76eebe212bb73707802a855d716fab6feb5 (diff)
Switch parsing core to syn
Diffstat (limited to 'src/nibble')
-rw-r--r--src/nibble/mod.rs926
-rw-r--r--src/nibble/parse/iter.rs149
-rw-r--r--src/nibble/parse/mod.rs61
3 files changed, 136 insertions, 1000 deletions
diff --git a/src/nibble/mod.rs b/src/nibble/mod.rs
index 5b51049..478e0cf 100644
--- a/src/nibble/mod.rs
+++ b/src/nibble/mod.rs
@@ -4,183 +4,85 @@ use crate::ast::{
self,
convert::{Context, Convert},
};
-
-use self::parse::{
- iter::{Peek, PeekExt, PeekableTakeWhile},
- requires, Parse, ParseError,
+use syn::ext::IdentExt;
+use syn::punctuated::Punctuated;
+use syn::{
+ parse::{Parse, ParseStream},
+ token, Result,
};
-use std::convert::Infallible;
-use std::str::FromStr;
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct Ident {
- name: String,
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub struct Epsilon {
+ paren_tok: token::Paren,
}
-impl Parse for Ident {
- type Err = Infallible;
-
- fn parse<I: Peek<Item = char>>(mut iter: I) -> Result<Self, ParseError<Self::Err>> {
- // rustc can't predict type, but rust-analyzer can...
- let name: String = PeekableTakeWhile::new(&mut iter, |c| c.is_alphabetic()).collect();
- if name.is_empty() {
- if let Some(c) = iter.peek() {
- Err(ParseError::InvalidCharacter(*c))
- } else {
- Err(ParseError::EndOfFile)
- }
+impl Parse for Epsilon {
+ fn parse(input: ParseStream<'_>) -> Result<Self> {
+ let content;
+ let paren_tok = syn::parenthesized!(content in input);
+ if content.is_empty() {
+ Ok(Self { paren_tok })
} else {
- Ok(Self { name })
+ Err(content.error("expected empty parentheses"))
}
}
}
-impl FromStr for Ident {
- type Err = ParseError<<Ident as Parse>::Err>;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- Ident::parse(s.chars().peekable())
+impl Convert for Epsilon {
+ fn convert(self, _: &Context) -> ast::Term {
+ ast::Term::Epsilon
}
}
+type Ident = syn::Ident;
+
impl Convert for Ident {
fn convert(self, context: &Context) -> ast::Term {
use ast::Term;
+ let name = self.to_string();
- if let Some(variable) = context.get(&self.name) {
+ if let Some(variable) = context.get(&name) {
Term::Variable(variable)
} else {
- Term::Call(self.name, vec![])
+ Term::Call(name, vec![])
}
}
}
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct Literal {
- contents: String,
-}
-
-impl Parse for Literal {
- type Err = LiteralError;
-
- fn parse<I: Peek<Item = char>>(mut iter: I) -> Result<Self, ParseError<Self::Err>> {
- fn parse_digit<I: Peek<Item = char>>(
- mut iter: I,
- radix: u32,
- ) -> Result<u32, ParseError<LiteralError>> {
- Ok(iter
- .next_if(|c| c.is_digit(radix))
- .ok_or(ParseError::EndOfFile)?
- .map_err(|i| ParseError::InvalidCharacter(*i.peek().unwrap()))?
- .to_digit(radix)
- .unwrap())
- }
-
- /// Parse full character escape.
- fn parse_escape<I: Peek<Item = char>>(
- mut iter: I,
- ) -> Result<char, ParseError<LiteralError>> {
- requires(&mut iter, '\\')?;
- match iter.peek().ok_or(ParseError::EndOfFile)? {
- '\'' | '\"' | '\\' => Ok(iter.next().unwrap()),
- 'n' => {
- requires(iter, 'n')?;
- Ok('\n')
- }
- 'r' => {
- requires(iter, 'r')?;
- Ok('\r')
- }
- 't' => {
- requires(iter, 't')?;
- Ok('\t')
- }
- '0' => {
- requires(iter, '0')?;
- Ok('\0')
- }
- 'x' => {
- requires(&mut iter, 'x')?;
- let upper = parse_digit(&mut iter, 8)?;
- let ord = 16 * upper + parse_digit(&mut iter, 16)?;
- std::char::from_u32(ord)
- .ok_or(ParseError::Other(LiteralError::InvalidCharacterCode(ord)))
- }
- 'u' => {
- requires(&mut iter, 'u')?;
- requires(&mut iter, '{')?;
- let mut ord = 0;
- for _ in 1..=6 {
- ord *= 16;
- ord += parse_digit(&mut iter, 16)?;
- iter.advance_while(|&c| c == '_');
-
- if iter.peek() == Some(&'}') {
- break;
- }
- }
- requires(&mut iter, '}')?;
-
- std::char::from_u32(ord)
- .ok_or(ParseError::Other(LiteralError::InvalidCharacterCode(ord)))
- }
- &c => Err(ParseError::InvalidCharacter(c)),
- }
- }
-
- requires(&mut iter, '\'')?;
-
- let mut s = String::new();
-
- loop {
- match iter.peek().ok_or(ParseError::EndOfFile)? {
- '\'' => {
- iter.next();
- return if s.is_empty() {
- Err(ParseError::Other(LiteralError::EmptyLiteral))
- } else {
- Ok(Literal { contents: s })
- };
- }
- &c @ '\n' | &c @ '\r' | &c @ '\t' => return Err(ParseError::InvalidCharacter(c)),
- '\\' => s.push(parse_escape(&mut iter)?),
- _ => s.push(iter.next().unwrap()),
- }
- }
- }
-}
+type Literal = syn::LitStr;
impl Convert for Literal {
fn convert(self, _context: &Context) -> ast::Term {
- ast::Term::Literal(self.contents)
+ ast::Term::Literal(self.value())
}
}
-impl FromStr for Literal {
- type Err = ParseError<<Literal as Parse>::Err>;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- Literal::parse(s.chars().peekable())
- }
-}
-
-#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-pub enum LiteralError {
- EmptyLiteral,
- InvalidCharacterCode(u32),
-}
-
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Call {
name: Ident,
- args: Vec<Expression>,
+ paren_tok: token::Paren,
+ args: Punctuated<Expression, syn::Token![,]>,
+}
+
+impl Parse for Call {
+ fn parse(input: ParseStream<'_>) -> Result<Self> {
+ let args;
+ let name = input.call(Ident::parse_any)?;
+ let paren_tok = syn::parenthesized!(args in input);
+ let args = args.call(Punctuated::parse_terminated)?;
+ Ok(Self {
+ name,
+ paren_tok,
+ args,
+ })
+ }
}
impl Convert for Call {
fn convert(self, context: &Context) -> ast::Term {
use ast::Term;
Term::Call(
- self.name.name,
+ self.name.to_string(),
self.args.into_iter().map(|e| e.convert(context)).collect(),
)
}
@@ -188,31 +90,27 @@ impl Convert for Call {
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Fix {
+ bracket_token: token::Bracket,
arg: Ident,
+ paren_token: token::Paren,
expr: Expression,
}
impl Parse for Fix {
- type Err = FixError;
-
- fn parse<I: Peek<Item = char>>(mut iter: I) -> Result<Self, ParseError<Self::Err>> {
- requires(&mut iter, '[')?;
- let arg = Ident::parse(&mut iter).map_err(ParseError::map)?;
- requires(&mut iter, ']')?;
- requires(&mut iter, '(')?;
- iter.advance_while(|c| c.is_whitespace());
- let expr = Expression::parse(Box::new(&mut iter as &mut dyn Peek<Item = char>))
- .map_err(ParseError::map)?;
- requires(&mut iter, ')')?;
- Ok(Self { arg, expr })
- }
-}
-
-impl FromStr for Fix {
- type Err = ParseError<<Fix as Parse>::Err>;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- Fix::parse(s.chars().peekable())
+ fn parse(input: ParseStream<'_>) -> Result<Self> {
+ let arg;
+ let bracket_token = syn::bracketed!(arg in input);
+ let arg = arg.call(Ident::parse_any)?;
+
+ let expr;
+ let paren_token = syn::parenthesized!(expr in input);
+ let expr = expr.parse()?;
+ Ok(Self {
+ bracket_token,
+ arg,
+ paren_token,
+ expr,
+ })
}
}
@@ -220,97 +118,86 @@ impl Convert for Fix {
fn convert(self, context: &Context) -> ast::Term {
use ast::Term;
let expr = self.expr;
- Term::Fix(Box::new(context.push(self.arg.name, |c| expr.convert(c))))
+ Term::Fix(Box::new(
+ context.push(self.arg.to_string(), |c| expr.convert(c)),
+ ))
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct FixError(Box<ExpressionError>);
+pub struct ParenExpression {
+ paren_tok: token::Paren,
+ expr: Expression,
+}
-impl From<Infallible> for FixError {
- fn from(other: Infallible) -> Self {
- match other {}
+impl Parse for ParenExpression {
+ fn parse(input: ParseStream<'_>) -> Result<Self> {
+ let expr;
+ let paren_tok = syn::parenthesized!(expr in input);
+ let expr = expr.parse()?;
+ Ok(Self { paren_tok, expr })
}
}
-impl From<ExpressionError> for FixError {
- fn from(other: ExpressionError) -> Self {
- Self(Box::new(other))
+impl Convert for ParenExpression {
+ fn convert(self, context: &Context) -> ast::Term {
+ self.expr.convert(context)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Term {
- Epsilon,
+ Epsilon(Epsilon),
Ident(Ident),
Literal(Literal),
Call(Call),
Fix(Fix),
- Parens(Expression),
+ Parens(ParenExpression),
}
impl Parse for Term {
- type Err = TermError;
-
- fn parse<I: Peek<Item = char>>(mut iter: I) -> Result<Self, ParseError<Self::Err>> {
- match iter.peek().ok_or(ParseError::EndOfFile)? {
- '(' => {
- iter.next();
-
- if iter.consume_if_next(&')') {
- Ok(Self::Epsilon)
- } else {
- let expr = Expression::parse(Box::new(&mut iter as &mut dyn Peek<Item = char>))
- .map_err(ParseError::map)?;
- requires(iter, ')').map(|_| Self::Parens(expr))
- }
+ fn parse(input: ParseStream<'_>) -> Result<Self> {
+ let lookahead = input.lookahead1();
+ if lookahead.peek(token::Paren) {
+ let content;
+ let paren_tok = syn::parenthesized!(content in input);
+ if content.is_empty() {
+ Ok(Self::Epsilon(Epsilon { paren_tok }))
+ } else {
+ content
+ .parse()
+ .map(|expr| Self::Parens(ParenExpression { paren_tok, expr }))
}
- '[' => Fix::parse(iter).map(Self::Fix).map_err(ParseError::map),
- '\'' => Literal::parse(iter)
- .map(Self::Literal)
- .map_err(ParseError::map),
- c if c.is_alphabetic() => {
- let ident = Ident::parse(&mut iter).map_err(ParseError::map)?;
-
- if iter.consume_if_next(&'(') {
- iter.advance_while(|c| c.is_whitespace());
- let mut args = Vec::new();
- loop {
- args.push(
- Expression::parse(Box::new(&mut iter as &mut dyn Peek<Item = char>))
- .map_err(ParseError::<CallError>::map)
- .map_err(ParseError::map)?,
- );
-
- if iter.consume_if_next(&')') {
- return Ok(Self::Call(Call { name: ident, args }));
- }
-
- requires(&mut iter, ',')?;
- iter.advance_while(|c| c.is_whitespace());
- }
- } else {
- Ok(Self::Ident(ident))
- }
+ } else if lookahead.peek(Ident::peek_any) {
+ let name = input.call(Ident::parse_any)?;
+ if input.peek(token::Paren) {
+ let args;
+ let paren_tok = syn::parenthesized!(args in input);
+ args.call(Punctuated::parse_terminated).map(|args| {
+ Self::Call(Call {
+ name,
+ paren_tok,
+ args,
+ })
+ })
+ } else {
+ Ok(Self::Ident(name))
}
- &c => Err(ParseError::InvalidCharacter(c)),
+ } else if lookahead.peek(token::Bracket) {
+ input.parse().map(Self::Fix)
+ } else if lookahead.peek(syn::LitStr) {
+ input.parse().map(Self::Literal)
+ } else {
+ Err(lookahead.error())
}
}
}
-impl FromStr for Term {
- type Err = ParseError<<Term as Parse>::Err>;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- Term::parse(s.chars().peekable())
- }
-}
-
impl Convert for Term {
fn convert(self, context: &Context) -> ast::Term {
use ast::Term;
match self {
- Self::Epsilon => Term::Epsilon,
+ Self::Epsilon(_) => Term::Epsilon,
Self::Ident(i) => i.convert(context),
Self::Literal(l) => l.convert(context),
Self::Call(c) => c.convert(context),
@@ -321,80 +208,15 @@ impl Convert for Term {
}
#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct CallError(Box<ExpressionError>);
-
-impl From<ExpressionError> for CallError {
- fn from(other: ExpressionError) -> Self {
- Self(Box::new(other))
- }
-}
-
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub enum TermError {
- Literal(LiteralError),
- Call(CallError),
- Fix(FixError),
- Parens(Box<ExpressionError>),
-}
-
-impl From<Infallible> for TermError {
- fn from(other: Infallible) -> Self {
- match other {}
- }
-}
-
-impl From<ExpressionError> for TermError {
- fn from(other: ExpressionError) -> Self {
- Self::Parens(Box::new(other))
- }
-}
-
-impl From<FixError> for TermError {
- fn from(other: FixError) -> Self {
- Self::Fix(other)
- }
-}
-
-impl From<CallError> for TermError {
- fn from(other: CallError) -> Self {
- Self::Call(other)
- }
-}
-
-impl From<LiteralError> for TermError {
- fn from(other: LiteralError) -> Self {
- Self::Literal(other)
- }
-}
-
-#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Cat {
- terms: Vec<Term>,
+ terms: Punctuated<Term, syn::Token![.]>,
}
impl Parse for Cat {
- type Err = SeqError;
-
- fn parse<I: Peek<Item = char>>(mut iter: I) -> Result<Self, ParseError<Self::Err>> {
- let mut terms = Vec::new();
- terms.push(Term::parse(&mut iter).map_err(ParseError::map)?);
- iter.advance_while(|c| c.is_whitespace());
-
- while iter.consume_if_next(&'.') {
- iter.advance_while(|c| c.is_whitespace());
- terms.push(Term::parse(&mut iter).map_err(ParseError::map)?);
- iter.advance_while(|c| c.is_whitespace());
- }
-
- Ok(Self { terms })
- }
-}
-
-impl FromStr for Cat {
- type Err = ParseError<<Cat as Parse>::Err>;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- Cat::parse(s.chars().peekable())
+ fn parse(input: ParseStream<'_>) -> Result<Self> {
+ Ok(Self {
+ terms: input.call(Punctuated::parse_separated_nonempty)?,
+ })
}
}
@@ -408,40 +230,15 @@ impl Convert for Cat {
}
#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct SeqError(TermError);
-
-impl From<TermError> for SeqError {
- fn from(other: TermError) -> Self {
- Self(other)
- }
-}
-
-#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Alt {
- cats: Vec<Cat>,
+ cats: Punctuated<Cat, syn::Token![|]>,
}
impl Parse for Alt {
- type Err = AltError;
-
- fn parse<I: Peek<Item = char>>(mut iter: I) -> Result<Self, ParseError<Self::Err>> {
- let mut seqs = Vec::new();
- seqs.push(Cat::parse(&mut iter).map_err(ParseError::map)?);
-
- while iter.consume_if_next(&'|') {
- iter.advance_while(|c| c.is_whitespace());
- seqs.push(Cat::parse(&mut iter).map_err(ParseError::map)?);
- }
-
- Ok(Self { cats: seqs })
- }
-}
-
-impl FromStr for Alt {
- type Err = ParseError<<Alt as Parse>::Err>;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- Alt::parse(s.chars().peekable())
+ fn parse(input: ParseStream<'_>) -> Result<Self> {
+ Ok(Self {
+ cats: input.call(Punctuated::parse_separated_nonempty)?,
+ })
}
}
@@ -454,485 +251,34 @@ impl Convert for Alt {
}
}
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct AltError(SeqError);
-
-impl From<SeqError> for AltError {
- fn from(other: SeqError) -> Self {
- Self(other)
- }
-}
-
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct Expression {
- alt: Alt,
-}
-
-impl Parse for Expression {
- type Err = ExpressionError;
-
- fn parse<I: Peek<Item = char>>(iter: I) -> Result<Self, ParseError<Self::Err>> {
- Ok(Self {
- alt: Alt::parse(iter).map_err(ParseError::map)?,
- })
- }
-}
-
-impl FromStr for Expression {
- type Err = ParseError<<Expression as Parse>::Err>;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- Expression::parse(s.chars().peekable())
- }
-}
-
-impl Convert for Expression {
- fn convert(self, context: &Context) -> ast::Term {
- self.alt.convert(context)
- }
-}
-
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct ExpressionError(AltError);
-
-impl From<AltError> for ExpressionError {
- fn from(other: AltError) -> Self {
- Self(other)
- }
-}
+type Expression = Alt;
#[cfg(test)]
mod tests {
- use super::{
- parse::{Parse, ParseError},
- Alt, Cat, Expression, Fix, Ident, Literal, LiteralError, Term,
- };
- use crate::ast::{
- self,
- convert::{Context, Convert},
- };
- use std::iter;
-
- macro_rules! parse_indent {
- ($s:literal) => {
- assert_eq!(
- $s.parse(),
- Ok(Ident {
- name: $s.to_owned()
- })
- )
- };
- }
-
- #[test]
- fn parse_ident_simple() {
- parse_indent!("x");
- parse_indent!("variable");
- }
-
- #[test]
- fn parse_indent_unicode() {
- parse_indent!("ℕꥇℜ");
- }
-
- #[test]
- fn parse_indent_stops() {
- let s = "variable then more text";
- let mut iter = s.chars().peekable();
- assert_eq!(
- Ident::parse(&mut iter),
- Ok(Ident {
- name: "variable".to_owned()
- })
- );
- assert_eq!(iter.collect::<String>(), " then more text");
- }
-
- #[test]
- fn parse_indent_not_empty() {
- assert_eq!("".parse::<Ident>(), Err(ParseError::EndOfFile));
- }
-
- #[test]
- fn parse_indent_alphabetic_only() {
- assert_eq!(
- "123".parse::<Ident>(),
- Err(ParseError::InvalidCharacter('1'))
- );
- }
-
- #[test]
- fn convert_ident_variable() {
- use ast::Term;
- let context = Context::new();
- context.push("x".to_owned(), |c| {
- assert_eq!(
- Ident {
- name: "x".to_owned()
- }
- .convert(c),
- Term::Variable(0)
- );
-
- c.push("y".to_owned(), |c| {
- assert_eq!(
- Ident {
- name: "y".to_owned()
- }
- .convert(c),
- Term::Variable(0)
- );
-
- assert_eq!(
- Ident {
- name: "x".to_owned()
- }
- .convert(c),
- Term::Variable(1)
- );
- })
- })
- }
-
- #[test]
- fn convert_ident_call() {
- use ast::Term;
- let context = Context::new();
-
- assert_eq!(
- Ident {
- name: "call".to_owned()
- }
- .convert(&context),
- Term::Call("call".to_owned(), vec![])
- );
-
- assert_eq!(
- Ident {
- name: "x".to_owned()
- }
- .convert(&context),
- Term::Call("x".to_owned(), vec![])
- );
-
- context.push("x".to_owned(), |c| {
- assert_eq!(
- Ident {
- name: "call".to_owned()
- }
- .convert(c),
- Term::Call("call".to_owned(), vec![])
- );
-
- assert_eq!(
- Ident {
- name: "x".to_owned()
- }
- .convert(c),
- Term::Variable(0)
- );
- })
- }
-
- macro_rules! parse_lit {
- ($str:literal) => {
- assert_eq!(
- Literal::parse(
- iter::once('\'')
- .chain($str.escape_debug())
- .chain(iter::once('\''),)
- .peekable()
- ),
- Ok(Literal {
- contents: $str.to_owned()
- }),
- )
- };
- }
-
- #[test]
- fn parse_literal_basic() {
- parse_lit!("hello");
- parse_lit!("this has whitespace");
- }
+ use syn::parse_str;
+ use super::{Epsilon, Ident, Literal};
#[test]
- fn parse_literal_quotes() {
- parse_lit!(r#"quote ' " and backslash \ quoting"#);
+ fn parse_epsilon() {
+ assert!(parse_str::<Epsilon>("()").is_ok());
+ assert!(parse_str::<Epsilon>("( )").is_ok());
+ assert!(parse_str::<Epsilon>("(").is_err());
+ assert!(parse_str::<Epsilon>(")").is_err());
+ assert!(parse_str::<Epsilon>("(x)").is_err());
}
#[test]
- fn parse_literal_ascii_escape() {
- parse_lit!("newline \n tab \t carriage return \r");
-
- assert_eq!(
- r#"'\0'"#.parse(),
- Ok(Literal {
- contents: "\0".to_owned()
- })
- );
+ fn parse_ident() {
+ assert_eq!(parse_str::<Ident>("x").unwrap().to_string(), "x");
+ assert_eq!(parse_str::<Ident>("x_yz").unwrap().to_string(), "x_yz");
+ assert_eq!(parse_str::<Ident>("a123").unwrap().to_string(), "a123");
+ assert_eq!(parse_str::<Ident>("𝒢𝒢").unwrap().to_string(), "𝒢𝒢");
+ assert!(parse_str::<Ident>("1").is_err());
+ assert!(parse_str::<Ident>("_").is_err());
}
#[test]
- fn parse_literal_hex_escape() {
- assert_eq!(
- r#"'\x20'"#.parse(),
- Ok(Literal {
- contents: " ".to_owned()
- })
- );
- }
-
- #[test]
- fn parse_literal_unicode_escape() {
- parse_lit!("emoji ❤ escape \u{200c}");
- }
-
- #[test]
- fn parse_literal_stops() {
- let mut iter = "'hi there' Not quoted".chars().peekable();
- assert_eq!(
- Literal::parse(&mut iter),
- Ok(Literal {
- contents: "hi there".to_owned()
- })
- );
- assert_eq!(iter.collect::<String>(), " Not quoted");
- }
-
- #[test]
- fn parse_literal_needs_quotes() {
- assert_eq!(
- "hello".parse::<Literal>(),
- Err(ParseError::InvalidCharacter('h'))
- );
- assert_eq!("'hello".parse::<Literal>(), Err(ParseError::EndOfFile));
- }
-
- #[test]
- fn parse_literal_not_empty() {
- assert_eq!(
- "''".parse::<Literal>(),
- Err(ParseError::Other(LiteralError::EmptyLiteral))
- );
- }
-
- #[test]
- fn parse_literal_no_forbidden_whitespace() {
- assert_eq!(
- "'\n".parse::<Literal>(),
- Err(ParseError::InvalidCharacter('\n'))
- );
- assert_eq!(
- "'\r".parse::<Literal>(),
- Err(ParseError::InvalidCharacter('\r'))
- );
- assert_eq!(
- "'\t".parse::<Literal>(),
- Err(ParseError::InvalidCharacter('\t'))
- );
- }
-
- #[test]
- fn parse_literal_invalid_escape() {
- assert_eq!(
- r#"'\F"#.parse::<Literal>(),
- Err(ParseError::InvalidCharacter('F'))
- );
- }
-
- #[test]
- fn parse_literal_bad_hex_escape() {
- assert_eq!(
- r#"'\xF"#.parse::<Literal>(),
- Err(ParseError::InvalidCharacter('F'))
- );
- assert_eq!(
- r#"'\x0z"#.parse::<Literal>(),
- Err(ParseError::InvalidCharacter('z'))
- );
- }
-
- #[test]
- fn parse_literal_bad_unicode_escape() {
- // Negative tests
- assert_eq!(
- r#"'\u0"#.parse::<Literal>(),
- Err(ParseError::InvalidCharacter('0'))
- );
- assert_eq!(
- r#"'\u{}"#.parse::<Literal>(),
- Err(ParseError::InvalidCharacter('}'))
- );
- assert_eq!(
- r#"'\u{z"#.parse::<Literal>(),
- Err(ParseError::InvalidCharacter('z'))
- );
- assert_eq!(
- r#"'\u{_"#.parse::<Literal>(),
- Err(ParseError::InvalidCharacter('_'))
- );
- assert_eq!(
- r#"'\u{0___h"#.parse::<Literal>(),
- Err(ParseError::InvalidCharacter('h'))
- );
- assert_eq!(
- r#"'\u{D800}"#.parse::<Literal>(),
- Err(ParseError::Other(LiteralError::InvalidCharacterCode(
- 0xd800
- )))
- );
- assert_eq!(
- r#"'\u{0000000"#.parse::<Literal>(),
- Err(ParseError::InvalidCharacter('0'))
- );
- }
-
- #[test]
- fn convert_literal() {
- use ast::Term;
- let context = Context::new();
- assert_eq!(
- Literal {
- contents: "hello".to_owned()
- }
- .convert(&context),
- Term::Literal("hello".to_owned())
- );
- }
-
- #[test]
- fn parse_fix_basic() {
- let expr = Expression {
- alt: Alt {
- cats: vec![Cat {
- terms: vec![Term::Epsilon],
- }],
- },
- };
- assert_eq!(
- "[x](())".parse(),
- Ok(Fix {
- arg: Ident {
- name: "x".to_owned()
- },
- expr: expr.clone()
- })
- );
- assert_eq!(
- "[x](() )".parse(),
- Ok(Fix {
- arg: Ident {
- name: "x".to_owned()
- },
- expr: expr.clone()
- })
- );
- assert_eq!(
- "[x]( ())".parse(),
- Ok(Fix {
- arg: Ident {
- name: "x".to_owned()
- },
- expr
- })
- );
- }
-
- #[test]
- fn parse_fix_needs_arg() {
- assert_eq!(
- "x]()".parse::<Fix>(),
- Err(ParseError::InvalidCharacter('x'))
- );
- assert_eq!(
- "[]()".parse::<Fix>(),
- Err(ParseError::InvalidCharacter(']'))
- );
- }
-
- #[test]
- fn parse_fix_needs_parens() {
- assert_eq!(
- "[x]x".parse::<Fix>(),
- Err(ParseError::InvalidCharacter('x'))
- );
- assert_eq!("[x](x".parse::<Fix>(), Err(ParseError::EndOfFile))
- }
-
- #[test]
- fn parse_fix_stops() {
- let mut iter = "[x](()) Not in fix".chars().peekable();
- assert_eq!(
- Fix::parse(&mut iter),
- Ok(Fix {
- arg: Ident {
- name: "x".to_owned()
- },
- expr: Expression {
- alt: Alt {
- cats: vec![Cat {
- terms: vec![Term::Epsilon],
- }],
- },
- },
- })
- );
-
- assert_eq!(iter.collect::<String>(), " Not in fix");
- }
-
- #[test]
- fn convert_fix_no_var() {
- let context = Context::new();
- let expr = Expression {
- alt: Alt {
- cats: vec![Cat {
- terms: vec![Term::Epsilon],
- }],
- },
- };
- assert_eq!(
- Fix {
- arg: Ident {
- name: "x".to_owned()
- },
- expr: expr.clone()
- }
- .convert(&context),
- ast::Term::Fix(Box::new(expr.convert(&context)))
- )
- }
-
- #[test]
- fn convert_fix_var() {
- use ast::Term;
- let context = Context::new();
- let expr = Expression {
- alt: Alt {
- cats: vec![Cat {
- terms: vec![super::Term::Ident(Ident {
- name: "x".to_owned(),
- })],
- }],
- },
- };
-
- assert_eq!(
- Fix {
- arg: Ident {
- name: "x".to_owned()
- },
- expr
- }
- .convert(&context),
- Term::Fix(Box::new(Term::Alt(
- Box::new(Term::Cat(
- Box::new(Term::Variable(0)),
- Box::new(Term::Epsilon)
- )),
- Box::new(Term::Bottom)
- )))
- )
+ fn parse_literal() {
+ assert_eq!(parse_str::<Literal>(r#""hello""#).unwrap().value(), "hello")
}
}
diff --git a/src/nibble/parse/iter.rs b/src/nibble/parse/iter.rs
deleted file mode 100644
index 1c635a3..0000000
--- a/src/nibble/parse/iter.rs
+++ /dev/null
@@ -1,149 +0,0 @@
-use std::iter::Peekable;
-
-pub use self::take_while::PeekableTakeWhile;
-
-/// An iterator with a `peek()` that returns an optional reference to the next
-/// element.
-///
-/// This trait is an extension of the `Peekable` type in the standard library.
-pub trait Peek: Iterator {
- /// Returns a reference to the [`next`] value without advancing the iterator.
- ///
- /// Like [`next`], if there is a value, it is wrapped in [`Some`]. But if
- /// iteration is over, [`None`] is returned.
- fn peek(&mut self) -> Option<&Self::Item>;
-}
-
-impl<I: Peek> Peek for &mut I {
- fn peek(&mut self) -> Option<&Self::Item> {
- I::peek(self)
- }
-}
-
-impl<I> Peek for Box<&mut dyn Peek<Item = I>> {
- fn peek(&mut self) -> Option<&Self::Item> {
- (**self).peek()
- }
-}
-
-impl<I: Iterator> Peek for Peekable<I> {
- fn peek(&mut self) -> Option<&Self::Item> {
- Peekable::peek(self)
- }
-}
-
-pub trait PeekExt: Peek {
- /// Returns the [`next`] value and advance the iterator only if `pred` is true.
- fn next_if<P: FnOnce(&Self::Item) -> bool>(
- &mut self,
- pred: P,
- ) -> Option<Result<Self::Item, &mut Self>> {
- let i = self.peek()?;
-
- if pred(i) {
- Some(Ok(self.next().unwrap()))
- } else {
- Some(Err(self))
- }
- }
-
- fn next_if_then<
- P: FnOnce(&Self::Item) -> bool,
- F: FnOnce(Self::Item) -> T,
- G: FnOnce(&mut Self) -> T,
- T,
- >(
- &mut self,
- pred: P,
- matched: F,
- unmatched: G,
- ) -> Option<T> {
- let i = self.peek()?;
-
- if pred(i) {
- Some(matched(self.next().unwrap()))
- } else {
- Some(unmatched(self))
- }
- }
-
- /// Advances the iterator only if the [`next`] item equals `val`.
- fn next_if_eq<T>(&mut self, val: &T) -> Option<Result<(), &Self::Item>>
- where
- Self::Item: PartialEq<T>,
- {
- self.next_if(|i| PartialEq::eq(i, val))
- .map(|r| r.map(|_| ()).map_err(|i| i.peek().unwrap()))
- }
-
- fn consume_if_next<T>(&mut self, val: &T) -> bool
- where
- Self::Item: PartialEq<T>,
- {
- self.next_if_eq(val).map(|r| r.is_ok()).unwrap_or(false)
- }
-
- /// Advance the iterator until `pred` is false, or `peek` returns [`None`].
- fn advance_while<P: FnMut(&Self::Item) -> bool>(&mut self, mut pred: P) {
- while self.next_if(&mut pred).map(|r| r.is_ok()).unwrap_or(false) {
- // Condition already loops for us
- }
- }
-}
-
-impl<I: Peek> PeekExt for I {}
-
-mod take_while {
- use super::Peek;
-
- #[derive(Clone, Debug, Eq, PartialEq)]
- pub struct PeekableTakeWhile<I, F> {
- inner: I,
- predicate: Option<F>,
- }
-
- impl<I, F> PeekableTakeWhile<I, F>
- where
- I: Peek,
- F: FnMut(&<I as Iterator>::Item) -> bool,
- {
- pub fn new(iter: I, predicate: F) -> Self {
- Self {
- inner: iter,
- predicate: Some(predicate),
- }
- }
- }
-
- impl<I, F> Iterator for PeekableTakeWhile<I, F>
- where
- I: Peek,
- F: FnMut(&<I as Iterator>::Item) -> bool,
- {
- type Item = <I as Iterator>::Item;
-
- fn next(&mut self) -> Option<Self::Item> {
- // Can't inline because of lifetimes
- let inner = &mut self.inner;
- if self.predicate.as_mut().and_then(|p| inner.peek().map(p))? {
- self.inner.next()
- } else {
- self.predicate = None;
- None
- }
- }
- }
-
- impl<I, F> Peek for PeekableTakeWhile<I, F>
- where
- I: Peek,
- F: FnMut(&<I as Iterator>::Item) -> bool,
- {
- fn peek(&mut self) -> Option<&Self::Item> {
- let inner = &mut self.inner;
- self.predicate
- .as_mut()
- .and_then(move |p| inner.peek().filter(|i| p(i)))
- }
- }
-}
diff --git a/src/nibble/parse/mod.rs b/src/nibble/parse/mod.rs
deleted file mode 100644
index 13c4c0e..0000000
--- a/src/nibble/parse/mod.rs
+++ /dev/null
@@ -1,61 +0,0 @@
-pub mod iter;
-
-use self::iter::Peek;
-use self::iter::PeekExt;
-
-#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-pub enum ParseError<E> {
- EndOfFile,
- InvalidCharacter(char),
- Other(E),
-}
-
-impl<U> ParseError<U> {
- pub fn map<T: Into<U>>(other: ParseError<T>) -> Self {
- match other {
- ParseError::EndOfFile => Self::EndOfFile,
- ParseError::InvalidCharacter(c) => Self::InvalidCharacter(c),
- ParseError::Other(t) => Self::Other(t.into())
- }
- }
-}
-
-/// Parse a value from an iterator.
-pub trait Parse: Sized {
- /// The associated error that can be returned from parsing.
- type Err;
- /// Parses `iter` to return a value of this type.
- ///
- /// This method may not reach the end of `iter`. If so, it must not consume any
- /// characters in the stream that do not form part of the value.
- ///
- /// If parsing succeeds, return the value inside [`Ok`]. Otherwise, when the
- /// iterator is ill-formatted, return an error specific to the inside [`Err`].
- /// The error type is specific to the implementation of the trait.
- fn parse<I: PeekExt<Item = char>>(iter: I) -> Result<Self, ParseError<Self::Err>>;
-}
-
-/// Consumes the next item in `iter` if it equals `c`. Otherwise, returns an
-/// appropriate error without advancing the iterator.
-///
-/// # Examples
-/// Basic usage
-/// ```
-/// use chomp::nibble::parse::{requires, Parse, ParseError};
-/// use std::convert::Infallible;
-///
-/// let s = "hello".to_owned();
-/// let mut iter = s.chars().peekable();
-/// assert_eq!(requires::<_, Infallible>(&mut iter, 'h'), Ok(()));
-/// assert_eq!(requires::<_, Infallible>(&mut iter, 'e'), Ok(()));
-/// assert_eq!(requires::<_, Infallible>(&mut iter, 'z'), Err(ParseError::InvalidCharacter('l')));
-/// assert_eq!(iter.next(), Some('l'));
-/// assert_eq!(iter.next(), Some('l'));
-/// assert_eq!(iter.next(), Some('o'));
-/// assert_eq!(requires::<_, Infallible>(iter, '!'), Err(ParseError::EndOfFile));
-/// ```
-pub fn requires<I: Peek<Item = char>, E>(mut iter: I, c: char) -> Result<(), ParseError<E>> {
- iter.next_if_eq(&c)
- .ok_or(ParseError::EndOfFile)?
- .map_err(|&c| ParseError::InvalidCharacter(c))
-}