use core::iter::{FromIterator, FusedIterator};
use core::ops::{self, Add, AddAssign, Index, IndexMut};
use core::ptr;
-use core::str as core_str;
use core::str::pattern::Pattern;
+use std_unicode::lossy;
use std_unicode::char::{decode_utf16, REPLACEMENT_CHARACTER};
use borrow::{Cow, ToOwned};
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
pub fn from_utf8_lossy<'a>(v: &'a [u8]) -> Cow<'a, str> {
- let mut i;
- match str::from_utf8(v) {
- Ok(s) => return Cow::Borrowed(s),
- Err(e) => i = e.valid_up_to(),
- }
+ let mut iter = lossy::Utf8Lossy::from_bytes(v).chunks();
- const TAG_CONT_U8: u8 = 128;
- const REPLACEMENT: &'static [u8] = b"\xEF\xBF\xBD"; // U+FFFD in UTF-8
- let total = v.len();
- fn unsafe_get(xs: &[u8], i: usize) -> u8 {
- unsafe { *xs.get_unchecked(i) }
- }
- fn safe_get(xs: &[u8], i: usize, total: usize) -> u8 {
- if i >= total { 0 } else { unsafe_get(xs, i) }
- }
+ let (first_valid, first_broken) = if let Some(chunk) = iter.next() {
+ let lossy::Utf8LossyChunk { valid, broken } = chunk;
+ if valid.len() == v.len() {
+ debug_assert!(broken.is_empty());
+ return Cow::Borrowed(valid);
+ }
+ (valid, broken)
+ } else {
+ return Cow::Borrowed("");
+ };
- let mut res = String::with_capacity(total);
+ const REPLACEMENT: &'static str = "\u{FFFD}";
- if i > 0 {
- unsafe { res.as_mut_vec().extend_from_slice(&v[..i]) };
+ let mut res = String::with_capacity(v.len());
+ res.push_str(first_valid);
+ if !first_broken.is_empty() {
+ res.push_str(REPLACEMENT);
}
- // subseqidx is the index of the first byte of the subsequence we're
- // looking at. It's used to copy a bunch of contiguous good codepoints
- // at once instead of copying them one by one.
- let mut subseqidx = i;
-
- while i < total {
- let i_ = i;
- let byte = unsafe_get(v, i);
- i += 1;
-
- macro_rules! error { () => ({
- unsafe {
- if subseqidx != i_ {
- res.as_mut_vec().extend_from_slice(&v[subseqidx..i_]);
- }
- subseqidx = i;
- res.as_mut_vec().extend_from_slice(REPLACEMENT);
- }
- })}
-
- if byte < 128 {
- // subseqidx handles this
- } else {
- let w = core_str::utf8_char_width(byte);
-
- match w {
- 2 => {
- if safe_get(v, i, total) & 192 != TAG_CONT_U8 {
- error!();
- continue;
- }
- i += 1;
- }
- 3 => {
- match (byte, safe_get(v, i, total)) {
- (0xE0, 0xA0...0xBF) => (),
- (0xE1...0xEC, 0x80...0xBF) => (),
- (0xED, 0x80...0x9F) => (),
- (0xEE...0xEF, 0x80...0xBF) => (),
- _ => {
- error!();
- continue;
- }
- }
- i += 1;
- if safe_get(v, i, total) & 192 != TAG_CONT_U8 {
- error!();
- continue;
- }
- i += 1;
- }
- 4 => {
- match (byte, safe_get(v, i, total)) {
- (0xF0, 0x90...0xBF) => (),
- (0xF1...0xF3, 0x80...0xBF) => (),
- (0xF4, 0x80...0x8F) => (),
- _ => {
- error!();
- continue;
- }
- }
- i += 1;
- if safe_get(v, i, total) & 192 != TAG_CONT_U8 {
- error!();
- continue;
- }
- i += 1;
- if safe_get(v, i, total) & 192 != TAG_CONT_U8 {
- error!();
- continue;
- }
- i += 1;
- }
- _ => {
- error!();
- continue;
- }
- }
+ for lossy::Utf8LossyChunk { valid, broken } in iter {
+ res.push_str(valid);
+ if !broken.is_empty() {
+ res.push_str(REPLACEMENT);
}
}
- if subseqidx < total {
- unsafe { res.as_mut_vec().extend_from_slice(&v[subseqidx..total]) };
- }
+
Cow::Owned(res)
}
test = false
bench = false
+[[test]]
+name = "std_unicode_tests"
+path = "tests/lib.rs"
+
[dependencies]
core = { path = "../libcore" }
#![feature(char_escape_debug)]
#![feature(core_char_ext)]
+#![feature(str_internals)]
+#![feature(core_intrinsics)]
#![feature(decode_utf8)]
#![feature(fused)]
#![feature(fn_traits)]
mod tables;
mod u_str;
pub mod char;
+pub mod lossy;
#[allow(deprecated)]
pub mod str {
--- /dev/null
+// 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 as core_str;
+use core::fmt;
+use core::fmt::Write;
+use char;
+use core::intrinsics;
+
+
+/// 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 { intrinsics::transmute(bytes) }
+ }
+
+ pub fn chunks(&self) -> Utf8LossyChunksIter {
+ Utf8LossyChunksIter { source: &self.bytes }
+ }
+}
+
+
+/// Iterator over lossy UTF-8 string
+#[unstable(feature = "str_internals", issue = "0")]
+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 {
+ for Utf8LossyChunk { valid, broken } in self.chunks() {
+ 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('"')
+ }
+}
--- /dev/null
+// 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.
+
+#![feature(str_internals, unicode)]
+
+extern crate std_unicode;
+
+mod lossy;
--- /dev/null
+// 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 std_unicode::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")));
+}