Skip to content

Commit

Permalink
Add primitive for consuming an empty line (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
scouten authored Dec 27, 2023
1 parent 97a9038 commit ffb1b32
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 1 deletion.
16 changes: 16 additions & 0 deletions src/primitives/line.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,22 @@ pub(crate) fn non_empty_line(input: Span<'_>) -> IResult<Span, Span> {
})
}

/// Consumes an empty line.
///
/// An empty line may contain any number of white space characters.
///
/// Returns an error if the line contains any non-white-space characters.
#[allow(dead_code)]
pub(crate) fn empty_line(input: Span<'_>) -> IResult<Span, Span> {
let (i, line) = line(input)?;

if line.data().bytes().all(nom::character::is_space) {
Ok((i, line))
} else {
Err(Err::Error(Error::new(input, ErrorKind::NonEmpty)))
}
}

fn trim_rem_start_matches<'a>(rem_inp: (Span<'a>, Span<'a>), c: char) -> (Span<'a>, Span<'a>) {
if let Some(rem) = rem_inp.0.strip_prefix(c) {
let prefix_len = rem_inp.0.len() - rem.len();
Expand Down
2 changes: 1 addition & 1 deletion src/primitives/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
mod line;
#[allow(unused_imports)]
pub(crate) use line::{line, non_empty_line, normalized_line};
pub(crate) use line::{empty_line, line, non_empty_line, normalized_line};

/// Represents a subset of the overall input stream.
///
Expand Down
124 changes: 124 additions & 0 deletions src/tests/primitives/line.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,3 +321,127 @@ mod non_empty_line {
assert_eq!(*line.data(), "abc \rdef");
}
}

mod empty_line {
use nom::{
error::{Error, ErrorKind},
Err,
};

use crate::{primitives::empty_line, Span};

#[test]
fn empty_source() {
let (rem, line) = empty_line(Span::new("", true)).unwrap();

assert_eq!(rem, Span::new("", true));
assert_eq!(line, Span::new("", true));
}

#[test]
fn simple_line() {
let expected_err: Err<Error<nom_span::Spanned<&str>>> =
Err::Error(Error::new(Span::new("abc", true), ErrorKind::NonEmpty));

let actual_err = empty_line(Span::new("abc", true)).unwrap_err();

assert_eq!(expected_err, actual_err);
}

#[test]
fn leading_space() {
let expected_err: Err<Error<nom_span::Spanned<&str>>> =
Err::Error(Error::new(Span::new(" abc", true), ErrorKind::NonEmpty));

let actual_err = empty_line(Span::new(" abc", true)).unwrap_err();

assert_eq!(expected_err, actual_err);
}

#[test]
fn consumes_spaces() {
// Should consume a source containing only spaces.

let (rem, line) = empty_line(Span::new(" ", true)).unwrap();

assert_eq!(rem.line(), 1);
assert_eq!(rem.col(), 6);
assert_eq!(*rem.data(), "");

assert_eq!(line.line(), 1);
assert_eq!(line.col(), 1);
assert_eq!(*line.data(), " ");
}

#[test]
fn consumes_spaces_and_tabs() {
// Should consume a source containing only spaces.

let (rem, line) = empty_line(Span::new(" \t ", true)).unwrap();

assert_eq!(rem.line(), 1);
assert_eq!(rem.col(), 6);
assert_eq!(*rem.data(), "");

assert_eq!(line.line(), 1);
assert_eq!(line.col(), 1);
assert_eq!(*line.data(), " \t ");
}

#[test]
fn consumes_lf() {
// Should consume but not return \n.

let (rem, line) = empty_line(Span::new(" \ndef", true)).unwrap();

assert_eq!(rem.line(), 2);
assert_eq!(rem.col(), 1);
assert_eq!(*rem.data(), "def");

assert_eq!(line.line(), 1);
assert_eq!(line.col(), 1);
assert_eq!(*line.data(), " ");
}

#[test]
fn consumes_crlf() {
// Should consume but not return \r\n.

let (rem, line) = empty_line(Span::new(" \r\ndef", true)).unwrap();

assert_eq!(rem.line(), 2);
assert_eq!(rem.col(), 1);
assert_eq!(*rem.data(), "def");

assert_eq!(line.line(), 1);
assert_eq!(line.col(), 1);
assert_eq!(*line.data(), " ");
}

#[test]
fn doesnt_consume_lfcr() {
// Should consume \n but not a subsequent \r.

let (rem, line) = empty_line(Span::new(" \n\rdef", true)).unwrap();

assert_eq!(rem.line(), 2);
assert_eq!(rem.col(), 1);
assert_eq!(*rem.data(), "\rdef");

assert_eq!(line.line(), 1);
assert_eq!(line.col(), 1);
assert_eq!(*line.data(), " ");
}

#[test]
fn standalone_cr_doesnt_end_line() {
// A "line" with \r and no immediate \n is not considered empty.

let expected_err: Err<Error<nom_span::Spanned<&str>>> =
Err::Error(Error::new(Span::new(" \rdef", true), ErrorKind::NonEmpty));

let actual_err = empty_line(Span::new(" \rdef", true)).unwrap_err();

assert_eq!(expected_err, actual_err);
}
}

0 comments on commit ffb1b32

Please sign in to comment.