Repository: winnow-rs/winnow (Rust)

Issue:
Winnow and nom produce differing outcomes when running identical code when use take_until 
### Please complete the following tasks

- [X] I have searched the [discussions](https://github.com/winnow-rs/winnow/discussions)
- [X] I have searched the [open](https://github.com/winnow-rs/winnow/issues) and [rejected](https://github.com/winnow-rs/winnow/issues?q=is%3Aissue+label%3AS-wont-fix+is%3Aclosed) issues

### rust version

rustc 1.74.0 (79e9716c9 2023-11-13)

### winnow version

0.6.2

### Minimal reproducible code

```diff
use winnow::error::ErrMode::Backtrack;

fn main() {
    test_winnow();
    test_nom();
}

fn test_winnow() {
    use winnow::ascii::multispace0;
    use winnow::error::ContextError;
    use winnow::token::{literal, take_until};
    use winnow::Parser;

    let result = (
        multispace0::<&str, ContextError>,
        literal("}"),
+        take_until(0.., ""),
        literal(""),
    ).parse_peek("}");
+    assert_eq!(result, Err(Backtrack(ContextError::new())))
}

fn test_nom() {
    use nom::bytes::complete::{tag, take_until};
    use nom::character::complete::multispace0;
    use nom::error::VerboseError;
    use nom::sequence::tuple;

    let result = tuple((
        multispace0::<&str, VerboseError<_>>,
        tag("}"),
+        take_until(""),
        tag(""),
    ))("}");
+    assert_eq!(result, Ok(("", ("", "}", "",""))));
}
```

```toml
[dependencies]
nom = "7.1.3"
winnow = {version = "0.6",features = ["debug"]}
```


### Steps to reproduce the bug with the above code

cargo run 

### Actual Behaviour

In the example provided above: winnow and nom have the same code logic, but they yield inconsistent results upon execution. Currently, I have ported the internal code from nom to winnow; however, despite maintaining the same logic, winnow throws errors during execution while nom successfully completes.

### Expected Behaviour

Anticipating consistency in the logic between the two.



### Additional Context

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=2a9b80d71c7077990cb025305b290fcb


Discussion:
Looks like we have inconsistent behavior between `simd` feature being on and off.

I am curious why you have have `take_until("")` and `literal("")`
@epage Thanks for your quick fix.



Patch:
diff --git a/src/stream/mod.rs b/src/stream/mod.rs
index 9ccfc7299..7aa256893 100644
--- a/src/stream/mod.rs
+++ b/src/stream/mod.rs
@@ -3478,19 +3478,19 @@ fn memchr3(token: (u8, u8, u8), slice: &[u8]) -> Option<usize> {
 
 #[inline(always)]
 fn memmem(slice: &[u8], literal: &[u8]) -> Option<crate::lib::std::ops::Range<usize>> {
-    if literal.len() == 1 {
-        memchr(literal[0], slice).map(|i| i..i + 1)
-    } else {
-        memmem_(slice, literal)
+    match literal.len() {
+        0 => Some(0..0),
+        1 => memchr(literal[0], slice).map(|i| i..i + 1),
+        _ => memmem_(slice, literal),
     }
 }
 
 #[inline(always)]
 fn memmem2(slice: &[u8], literal: (&[u8], &[u8])) -> Option<crate::lib::std::ops::Range<usize>> {
-    if literal.0.len() == 1 && literal.1.len() == 1 {
-        memchr2((literal.0[0], literal.1[0]), slice).map(|i| i..i + 1)
-    } else {
-        memmem2_(slice, literal)
+    match (literal.0.len(), literal.1.len()) {
+        (0, _) | (_, 0) => Some(0..0),
+        (1, 1) => memchr2((literal.0[0], literal.1[0]), slice).map(|i| i..i + 1),
+        _ => memmem2_(slice, literal),
     }
 }
 
@@ -3499,10 +3499,10 @@ fn memmem3(
     slice: &[u8],
     literal: (&[u8], &[u8], &[u8]),
 ) -> Option<crate::lib::std::ops::Range<usize>> {
-    if literal.0.len() == 1 && literal.1.len() == 1 && literal.2.len() == 1 {
-        memchr3((literal.0[0], literal.1[0], literal.2[0]), slice).map(|i| i..i + 1)
-    } else {
-        memmem3_(slice, literal)
+    match (literal.0.len(), literal.1.len(), literal.2.len()) {
+        (0, _, _) | (_, 0, _) | (_, _, 0) => Some(0..0),
+        (1, 1, 1) => memchr3((literal.0[0], literal.1[0], literal.2[0]), slice).map(|i| i..i + 1),
+        _ => memmem3_(slice, literal),
     }
 }
 


Test patch:
diff --git a/src/token/tests.rs b/src/token/tests.rs
index 23f1cc0f9..cd55c014a 100644
--- a/src/token/tests.rs
+++ b/src/token/tests.rs
@@ -97,6 +97,15 @@ fn complete_take_until() {
     );
 }
 
+#[test]
+fn complete_take_until_empty() {
+    fn take_until_empty(i: &str) -> IResult<&str, &str> {
+        take_until(0, "").parse_peek(i)
+    }
+    assert_eq!(take_until_empty(""), Ok(("", "")));
+    assert_eq!(take_until_empty("end"), Ok(("end", "")));
+}
+
 #[test]
 fn complete_literal_case_insensitive() {
     fn caseless_bytes(i: &[u8]) -> IResult<&[u8], &[u8]> {