about summary refs log tree commit diff
path: root/src/libcore
diff options
context:
space:
mode:
authorSimon Sapin <simon.sapin@exyr.org>2018-04-05 15:55:28 +0200
committerSimon Sapin <simon.sapin@exyr.org>2018-04-12 00:13:43 +0200
commitf87d4a15a82a76e7510629173c366d084f2c02ca (patch)
tree0fe7c0452bcb154424472556bfc5baf8d0388cf2 /src/libcore
parentae6adf335c88cad4e4a1805a805ac49dd1350174 (diff)
downloadrust-f87d4a15a82a76e7510629173c366d084f2c02ca.tar.gz
rust-f87d4a15a82a76e7510629173c366d084f2c02ca.zip
Move Utf8Lossy decoder to libcore
Diffstat (limited to 'src/libcore')
-rw-r--r--src/libcore/str/lossy.rs212
-rw-r--r--src/libcore/str/mod.rs4
-rw-r--r--src/libcore/tests/lib.rs2
-rw-r--r--src/libcore/tests/str_lossy.rs91
4 files changed, 309 insertions, 0 deletions
diff --git a/src/libcore/str/lossy.rs b/src/libcore/str/lossy.rs
new file mode 100644
index 00000000000..30b7267da7c
--- /dev/null
+++ b/src/libcore/str/lossy.rs
@@ -0,0 +1,212 @@
+// Copyright 2012-2017 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use char;
+use str as core_str;
+use fmt;
+use fmt::Write;
+use mem;
+
+/// Lossy UTF-8 string.
+#[unstable(feature = "str_internals", issue = "0")]
+pub struct Utf8Lossy {
+    bytes: [u8]
+}
+
+impl Utf8Lossy {
+    pub fn from_str(s: &str) -> &Utf8Lossy {
+        Utf8Lossy::from_bytes(s.as_bytes())
+    }
+
+    pub fn from_bytes(bytes: &[u8]) -> &Utf8Lossy {
+        unsafe { mem::transmute(bytes) }
+    }
+
+    pub fn chunks(&self) -> Utf8LossyChunksIter {
+        Utf8LossyChunksIter { source: &self.bytes }
+    }
+}
+
+
+/// Iterator over lossy UTF-8 string
+#[unstable(feature = "str_internals", issue = "0")]
+#[allow(missing_debug_implementations)]
+pub struct Utf8LossyChunksIter<'a> {
+    source: &'a [u8],
+}
+
+#[unstable(feature = "str_internals", issue = "0")]
+#[derive(PartialEq, Eq, Debug)]
+pub struct Utf8LossyChunk<'a> {
+    /// Sequence of valid chars.
+    /// Can be empty between broken UTF-8 chars.
+    pub valid: &'a str,
+    /// Single broken char, empty if none.
+    /// Empty iff iterator item is last.
+    pub broken: &'a [u8],
+}
+
+impl<'a> Iterator for Utf8LossyChunksIter<'a> {
+    type Item = Utf8LossyChunk<'a>;
+
+    fn next(&mut self) -> Option<Utf8LossyChunk<'a>> {
+        if self.source.len() == 0 {
+            return None;
+        }
+
+        const TAG_CONT_U8: u8 = 128;
+        fn unsafe_get(xs: &[u8], i: usize) -> u8 {
+            unsafe { *xs.get_unchecked(i) }
+        }
+        fn safe_get(xs: &[u8], i: usize) -> u8 {
+            if i >= xs.len() { 0 } else { unsafe_get(xs, i) }
+        }
+
+        let mut i = 0;
+        while i < self.source.len() {
+            let i_ = i;
+
+            let byte = unsafe_get(self.source, i);
+            i += 1;
+
+            if byte < 128 {
+
+            } else {
+                let w = core_str::utf8_char_width(byte);
+
+                macro_rules! error { () => ({
+                    unsafe {
+                        let r = Utf8LossyChunk {
+                            valid: core_str::from_utf8_unchecked(&self.source[0..i_]),
+                            broken: &self.source[i_..i],
+                        };
+                        self.source = &self.source[i..];
+                        return Some(r);
+                    }
+                })}
+
+                match w {
+                    2 => {
+                        if safe_get(self.source, i) & 192 != TAG_CONT_U8 {
+                            error!();
+                        }
+                        i += 1;
+                    }
+                    3 => {
+                        match (byte, safe_get(self.source, i)) {
+                            (0xE0, 0xA0 ... 0xBF) => (),
+                            (0xE1 ... 0xEC, 0x80 ... 0xBF) => (),
+                            (0xED, 0x80 ... 0x9F) => (),
+                            (0xEE ... 0xEF, 0x80 ... 0xBF) => (),
+                            _ => {
+                                error!();
+                            }
+                        }
+                        i += 1;
+                        if safe_get(self.source, i) & 192 != TAG_CONT_U8 {
+                            error!();
+                        }
+                        i += 1;
+                    }
+                    4 => {
+                        match (byte, safe_get(self.source, i)) {
+                            (0xF0, 0x90 ... 0xBF) => (),
+                            (0xF1 ... 0xF3, 0x80 ... 0xBF) => (),
+                            (0xF4, 0x80 ... 0x8F) => (),
+                            _ => {
+                                error!();
+                            }
+                        }
+                        i += 1;
+                        if safe_get(self.source, i) & 192 != TAG_CONT_U8 {
+                            error!();
+                        }
+                        i += 1;
+                        if safe_get(self.source, i) & 192 != TAG_CONT_U8 {
+                            error!();
+                        }
+                        i += 1;
+                    }
+                    _ => {
+                        error!();
+                    }
+                }
+            }
+        }
+
+        let r = Utf8LossyChunk {
+            valid: unsafe { core_str::from_utf8_unchecked(self.source) },
+            broken: &[],
+        };
+        self.source = &[];
+        return Some(r);
+    }
+}
+
+
+impl fmt::Display for Utf8Lossy {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        // If we're the empty string then our iterator won't actually yield
+        // anything, so perform the formatting manually
+        if self.bytes.len() == 0 {
+            return "".fmt(f)
+        }
+
+        for Utf8LossyChunk { valid, broken } in self.chunks() {
+            // If we successfully decoded the whole chunk as a valid string then
+            // we can return a direct formatting of the string which will also
+            // respect various formatting flags if possible.
+            if valid.len() == self.bytes.len() {
+                assert!(broken.is_empty());
+                return valid.fmt(f)
+            }
+
+            f.write_str(valid)?;
+            if !broken.is_empty() {
+                f.write_char(char::REPLACEMENT_CHARACTER)?;
+            }
+        }
+        Ok(())
+    }
+}
+
+impl fmt::Debug for Utf8Lossy {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        f.write_char('"')?;
+
+        for Utf8LossyChunk { valid, broken } in self.chunks() {
+
+            // Valid part.
+            // Here we partially parse UTF-8 again which is suboptimal.
+            {
+                let mut from = 0;
+                for (i, c) in valid.char_indices() {
+                    let esc = c.escape_debug();
+                    // If char needs escaping, flush backlog so far and write, else skip
+                    if esc.len() != 1 {
+                        f.write_str(&valid[from..i])?;
+                        for c in esc {
+                            f.write_char(c)?;
+                        }
+                        from = i + c.len_utf8();
+                    }
+                }
+                f.write_str(&valid[from..])?;
+            }
+
+            // Broken parts of string as hex escape.
+            for &b in broken {
+                write!(f, "\\x{:02x}", b)?;
+            }
+        }
+
+        f.write_char('"')
+    }
+}
diff --git a/src/libcore/str/mod.rs b/src/libcore/str/mod.rs
index 1185b7acaae..7a97d89dcf9 100644
--- a/src/libcore/str/mod.rs
+++ b/src/libcore/str/mod.rs
@@ -26,6 +26,10 @@ use mem;
 
 pub mod pattern;
 
+#[unstable(feature = "str_internals", issue = "0")]
+#[allow(missing_docs)]
+pub mod lossy;
+
 /// A trait to abstract the idea of creating a new instance of a type from a
 /// string.
 ///
diff --git a/src/libcore/tests/lib.rs b/src/libcore/tests/lib.rs
index c3162899bbd..149269263dc 100644
--- a/src/libcore/tests/lib.rs
+++ b/src/libcore/tests/lib.rs
@@ -33,6 +33,7 @@
 #![feature(sort_internals)]
 #![feature(specialization)]
 #![feature(step_trait)]
+#![feature(str_internals)]
 #![feature(test)]
 #![feature(trusted_len)]
 #![feature(try_trait)]
@@ -68,4 +69,5 @@ mod ptr;
 mod result;
 mod slice;
 mod str;
+mod str_lossy;
 mod tuple;
diff --git a/src/libcore/tests/str_lossy.rs b/src/libcore/tests/str_lossy.rs
new file mode 100644
index 00000000000..69e28256da9
--- /dev/null
+++ b/src/libcore/tests/str_lossy.rs
@@ -0,0 +1,91 @@
+// Copyright 2012-2017 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use core::str::lossy::*;
+
+#[test]
+fn chunks() {
+    let mut iter = Utf8Lossy::from_bytes(b"hello").chunks();
+    assert_eq!(Some(Utf8LossyChunk { valid: "hello", broken: b"", }), iter.next());
+    assert_eq!(None, iter.next());
+
+    let mut iter = Utf8Lossy::from_bytes("ศไทย中华Việt Nam".as_bytes()).chunks();
+    assert_eq!(Some(Utf8LossyChunk { valid: "ศไทย中华Việt Nam", broken: b"", }), iter.next());
+    assert_eq!(None, iter.next());
+
+    let mut iter = Utf8Lossy::from_bytes(b"Hello\xC2 There\xFF Goodbye").chunks();
+    assert_eq!(Some(Utf8LossyChunk { valid: "Hello", broken: b"\xC2", }), iter.next());
+    assert_eq!(Some(Utf8LossyChunk { valid: " There", broken: b"\xFF", }), iter.next());
+    assert_eq!(Some(Utf8LossyChunk { valid: " Goodbye", broken: b"", }), iter.next());
+    assert_eq!(None, iter.next());
+
+    let mut iter = Utf8Lossy::from_bytes(b"Hello\xC0\x80 There\xE6\x83 Goodbye").chunks();
+    assert_eq!(Some(Utf8LossyChunk { valid: "Hello", broken: b"\xC0", }), iter.next());
+    assert_eq!(Some(Utf8LossyChunk { valid: "", broken: b"\x80", }), iter.next());
+    assert_eq!(Some(Utf8LossyChunk { valid: " There", broken: b"\xE6\x83", }), iter.next());
+    assert_eq!(Some(Utf8LossyChunk { valid: " Goodbye", broken: b"", }), iter.next());
+    assert_eq!(None, iter.next());
+
+    let mut iter = Utf8Lossy::from_bytes(b"\xF5foo\xF5\x80bar").chunks();
+    assert_eq!(Some(Utf8LossyChunk { valid: "", broken: b"\xF5", }), iter.next());
+    assert_eq!(Some(Utf8LossyChunk { valid: "foo", broken: b"\xF5", }), iter.next());
+    assert_eq!(Some(Utf8LossyChunk { valid: "", broken: b"\x80", }), iter.next());
+    assert_eq!(Some(Utf8LossyChunk { valid: "bar", broken: b"", }), iter.next());
+    assert_eq!(None, iter.next());
+
+    let mut iter = Utf8Lossy::from_bytes(b"\xF1foo\xF1\x80bar\xF1\x80\x80baz").chunks();
+    assert_eq!(Some(Utf8LossyChunk { valid: "", broken: b"\xF1", }), iter.next());
+    assert_eq!(Some(Utf8LossyChunk { valid: "foo", broken: b"\xF1\x80", }), iter.next());
+    assert_eq!(Some(Utf8LossyChunk { valid: "bar", broken: b"\xF1\x80\x80", }), iter.next());
+    assert_eq!(Some(Utf8LossyChunk { valid: "baz", broken: b"", }), iter.next());
+    assert_eq!(None, iter.next());
+
+    let mut iter = Utf8Lossy::from_bytes(b"\xF4foo\xF4\x80bar\xF4\xBFbaz").chunks();
+    assert_eq!(Some(Utf8LossyChunk { valid: "", broken: b"\xF4", }), iter.next());
+    assert_eq!(Some(Utf8LossyChunk { valid: "foo", broken: b"\xF4\x80", }), iter.next());
+    assert_eq!(Some(Utf8LossyChunk { valid: "bar", broken: b"\xF4", }), iter.next());
+    assert_eq!(Some(Utf8LossyChunk { valid: "", broken: b"\xBF", }), iter.next());
+    assert_eq!(Some(Utf8LossyChunk { valid: "baz", broken: b"", }), iter.next());
+    assert_eq!(None, iter.next());
+
+    let mut iter = Utf8Lossy::from_bytes(b"\xF0\x80\x80\x80foo\xF0\x90\x80\x80bar").chunks();
+    assert_eq!(Some(Utf8LossyChunk { valid: "", broken: b"\xF0", }), iter.next());
+    assert_eq!(Some(Utf8LossyChunk { valid: "", broken: b"\x80", }), iter.next());
+    assert_eq!(Some(Utf8LossyChunk { valid: "", broken: b"\x80", }), iter.next());
+    assert_eq!(Some(Utf8LossyChunk { valid: "", broken: b"\x80", }), iter.next());
+    assert_eq!(Some(Utf8LossyChunk { valid: "foo\u{10000}bar", broken: b"", }), iter.next());
+    assert_eq!(None, iter.next());
+
+    // surrogates
+    let mut iter = Utf8Lossy::from_bytes(b"\xED\xA0\x80foo\xED\xBF\xBFbar").chunks();
+    assert_eq!(Some(Utf8LossyChunk { valid: "", broken: b"\xED", }), iter.next());
+    assert_eq!(Some(Utf8LossyChunk { valid: "", broken: b"\xA0", }), iter.next());
+    assert_eq!(Some(Utf8LossyChunk { valid: "", broken: b"\x80", }), iter.next());
+    assert_eq!(Some(Utf8LossyChunk { valid: "foo", broken: b"\xED", }), iter.next());
+    assert_eq!(Some(Utf8LossyChunk { valid: "", broken: b"\xBF", }), iter.next());
+    assert_eq!(Some(Utf8LossyChunk { valid: "", broken: b"\xBF", }), iter.next());
+    assert_eq!(Some(Utf8LossyChunk { valid: "bar", broken: b"", }), iter.next());
+    assert_eq!(None, iter.next());
+}
+
+#[test]
+fn display() {
+    assert_eq!(
+        "Hello\u{FFFD}\u{FFFD} There\u{FFFD} Goodbye",
+        &format!("{}", Utf8Lossy::from_bytes(b"Hello\xC0\x80 There\xE6\x83 Goodbye")));
+}
+
+#[test]
+fn debug() {
+    assert_eq!(
+        "\"Hello\\xc0\\x80 There\\xe6\\x83 Goodbye\\u{10d4ea}\"",
+        &format!("{:?}", Utf8Lossy::from_bytes(
+            b"Hello\xC0\x80 There\xE6\x83 Goodbye\xf4\x8d\x93\xaa")));
+}