Import unic-char-range 0.9.0 upstream upstream/0.9.0
authorDongHun Kwak <dh0128.kwak@samsung.com>
Thu, 4 May 2023 06:16:50 +0000 (15:16 +0900)
committerDongHun Kwak <dh0128.kwak@samsung.com>
Thu, 4 May 2023 06:16:50 +0000 (15:16 +0900)
15 files changed:
.cargo_vcs_info.json [new file with mode: 0644]
Cargo.toml [new file with mode: 0644]
Cargo.toml.orig [new file with mode: 0644]
benches/benchmarks.rs [new file with mode: 0644]
examples/macro_use_std_tests.rs [new file with mode: 0644]
src/iter.rs [new file with mode: 0644]
src/iter_fused.rs [new file with mode: 0644]
src/iter_trusted_len.rs [new file with mode: 0644]
src/lib.rs [new file with mode: 0644]
src/macros.rs [new file with mode: 0644]
src/par_iter.rs [new file with mode: 0644]
src/pkg_info.rs [new file with mode: 0644]
src/range.rs [new file with mode: 0644]
src/step.rs [new file with mode: 0644]
tests/iter_tests.rs [new file with mode: 0644]

diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
new file mode 100644 (file)
index 0000000..c44225f
--- /dev/null
@@ -0,0 +1,5 @@
+{
+  "git": {
+    "sha1": "5878605364af97a3358368a6eaef02104af2e016"
+  }
+}
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644 (file)
index 0000000..4273141
--- /dev/null
@@ -0,0 +1,51 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g. crates.io) dependencies
+#
+# If you believe there's an error in this file please file an
+# issue against the rust-lang/cargo repository. If you're
+# editing this file be aware that the upstream Cargo.toml
+# will likely look very different (and much more reasonable)
+
+[package]
+edition = "2018"
+name = "unic-char-range"
+version = "0.9.0"
+authors = ["The UNIC Project Developers"]
+exclude = []
+description = "UNIC — Unicode Character Tools — Character Range and Iteration"
+keywords = ["text", "unicode", "utilities", "iteration"]
+categories = ["internationalization", "text-processing"]
+license = "MIT/Apache-2.0"
+repository = "https://github.com/open-i18n/rust-unic/"
+[dependencies.rayon]
+version = "1.0"
+optional = true
+
+[features]
+default = []
+exact-size-is-empty = []
+fused = []
+std = []
+trusted-len = []
+unstable = ["exact-size-is-empty", "fused", "trusted-len"]
+[badges.appveyor]
+branch = "master"
+repository = "open-i18n/rust-unic"
+service = "github"
+
+[badges.is-it-maintained-issue-resolution]
+repository = "open-i18n/rust-unic"
+
+[badges.is-it-maintained-open-issues]
+repository = "open-i18n/rust-unic"
+
+[badges.maintenance]
+status = "actively-developed"
+
+[badges.travis-ci]
+branch = "master"
+repository = "open-i18n/rust-unic"
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
new file mode 100644 (file)
index 0000000..b2c3994
--- /dev/null
@@ -0,0 +1,33 @@
+[package]
+name = "unic-char-range"
+version = "0.9.0"
+edition = "2018"
+authors = ["The UNIC Project Developers"]
+repository = "https://github.com/open-i18n/rust-unic/"
+license = "MIT/Apache-2.0"
+description = "UNIC — Unicode Character Tools — Character Range and Iteration"
+keywords = ["text", "unicode", "utilities", "iteration"]
+categories = ["internationalization", "text-processing"]
+
+# No tests/benches that depends on /data/
+exclude = []
+
+[dependencies]
+rayon = { version = "1.0", optional = true }
+
+[features]
+default = []
+std = []
+
+# Unstable features
+unstable = ["exact-size-is-empty", "fused", "trusted-len"]
+exact-size-is-empty = []
+fused = []
+trusted-len = []
+
+[badges]
+maintenance = { status = "actively-developed" }
+is-it-maintained-issue-resolution = { repository = "open-i18n/rust-unic" }
+is-it-maintained-open-issues = { repository = "open-i18n/rust-unic" }
+appveyor = { repository = "open-i18n/rust-unic", branch = "master", service = "github" }
+travis-ci = { repository = "open-i18n/rust-unic", branch = "master" }
diff --git a/benches/benchmarks.rs b/benches/benchmarks.rs
new file mode 100644 (file)
index 0000000..a05ac27
--- /dev/null
@@ -0,0 +1,41 @@
+// Copyright 2017 The UNIC Project Developers.
+//
+// See the COPYRIGHT file at the top-level directory of this distribution.
+//
+// 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(test)]
+
+extern crate test;
+
+use std::char;
+use unic_char_range::CharRange;
+
+#[bench]
+fn forward_iteration(b: &mut test::Bencher) {
+    b.iter(|| CharRange::all().iter().count())
+}
+
+#[bench]
+fn forward_iteration_baseline(b: &mut test::Bencher) {
+    b.iter(|| (0..0x11_0000).filter_map(char::from_u32).count())
+}
+
+#[bench]
+fn reverse_iteration(b: &mut test::Bencher) {
+    b.iter(|| CharRange::all().iter().rev().count())
+}
+
+#[bench]
+fn reverse_iteration_baseline(b: &mut test::Bencher) {
+    b.iter(|| (0..0x11_0000).rev().filter_map(char::from_u32).count())
+}
+
+#[bench]
+fn range_length(b: &mut test::Bencher) {
+    b.iter(|| CharRange::all().len())
+}
diff --git a/examples/macro_use_std_tests.rs b/examples/macro_use_std_tests.rs
new file mode 100644 (file)
index 0000000..751f93a
--- /dev/null
@@ -0,0 +1,18 @@
+// Copyright 2017 The UNIC Project Developers.
+//
+// See the COPYRIGHT file at the top-level directory of this distribution.
+//
+// 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.
+
+#[macro_use]
+extern crate unic_char_range;
+
+fn main() {
+    assert!(chars!('\u{0}'..='\u{2}')
+        .iter()
+        .eq(['\u{0}', '\u{1}', '\u{2}'].iter().cloned()));
+}
diff --git a/src/iter.rs b/src/iter.rs
new file mode 100644 (file)
index 0000000..44901df
--- /dev/null
@@ -0,0 +1,151 @@
+// Copyright 2017 The UNIC Project Developers.
+//
+// See the COPYRIGHT file at the top-level directory of this distribution.
+//
+// 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::{char, ops};
+
+use crate::{step, CharRange};
+
+const SURROGATE_RANGE: ops::Range<u32> = 0xD800..0xE000;
+
+/// An iterator over a range of unicode code points.
+///
+/// Constructed via `CharRange::iter`. See `CharRange` for more information.
+#[derive(Clone, Debug)]
+pub struct CharIter {
+    /// The lowest uniterated character (inclusive).
+    ///
+    /// Iteration is finished if this is higher than `high`.
+    low: char,
+
+    /// The highest uniterated character (inclusive).
+    ///
+    /// Iteration is finished if this is lower than `low`.
+    high: char,
+}
+
+impl From<CharRange> for CharIter {
+    fn from(range: CharRange) -> CharIter {
+        CharIter {
+            low: range.low,
+            high: range.high,
+        }
+    }
+}
+
+impl From<CharIter> for CharRange {
+    fn from(iter: CharIter) -> CharRange {
+        CharRange {
+            low: iter.low,
+            high: iter.high,
+        }
+    }
+}
+
+impl CharIter {
+    #[inline]
+    #[allow(unsafe_code)]
+    // When stepping `self.low` forward would go over `char::MAX`,
+    // Set `self.high` to `'\0'` instead. It will have the same effect --
+    // consuming the last element from the iterator and ending iteration.
+    fn step_forward(&mut self) {
+        if self.low == char::MAX {
+            self.high = '\0'
+        } else {
+            self.low = unsafe { step::forward(self.low) }
+        }
+    }
+
+    #[inline]
+    #[allow(unsafe_code)]
+    // When stepping `self.high` backward would cause underflow,
+    // set `self.low` to `char::MAX` instead. It will have the same effect --
+    // consuming the last element from the iterator and ending iteration.
+    fn step_backward(&mut self) {
+        if self.high == '\0' {
+            self.low = char::MAX;
+        } else {
+            self.high = unsafe { step::backward(self.high) }
+        }
+    }
+
+    #[inline]
+    /// ExactSizeIterator::is_empty() for stable
+    fn is_finished(&self) -> bool {
+        self.low > self.high
+    }
+}
+
+impl Iterator for CharIter {
+    type Item = char;
+
+    #[inline]
+    fn next(&mut self) -> Option<char> {
+        if self.is_finished() {
+            return None;
+        }
+
+        let ch = self.low;
+        self.step_forward();
+        Some(ch)
+    }
+
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        let len = self.len();
+        (len, Some(len))
+    }
+
+    fn last(self) -> Option<char> {
+        if self.is_finished() {
+            None
+        } else {
+            Some(self.high)
+        }
+    }
+
+    fn max(self) -> Option<char> {
+        self.last()
+    }
+
+    fn min(mut self) -> Option<char> {
+        self.next()
+    }
+}
+
+impl DoubleEndedIterator for CharIter {
+    #[inline]
+    fn next_back(&mut self) -> Option<Self::Item> {
+        if self.is_finished() {
+            None
+        } else {
+            let ch = self.high;
+            self.step_backward();
+            Some(ch)
+        }
+    }
+}
+
+impl ExactSizeIterator for CharIter {
+    fn len(&self) -> usize {
+        if self.is_finished() {
+            return 0;
+        }
+        let naive_range = (self.low as u32)..(self.high as u32 + 1);
+        if naive_range.start <= SURROGATE_RANGE.start && SURROGATE_RANGE.end <= naive_range.end {
+            naive_range.len() - SURROGATE_RANGE.len()
+        } else {
+            naive_range.len()
+        }
+    }
+
+    #[cfg(feature = "exact-size-is-empty")]
+    fn is_empty(&self) -> bool {
+        self.is_finished()
+    }
+}
diff --git a/src/iter_fused.rs b/src/iter_fused.rs
new file mode 100644 (file)
index 0000000..f0a3fae
--- /dev/null
@@ -0,0 +1,15 @@
+// Copyright 2017 The UNIC Project Developers.
+//
+// See the COPYRIGHT file at the top-level directory of this distribution.
+//
+// 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::iter;
+
+use super::iter::CharIter;
+
+impl iter::FusedIterator for CharIter {}
diff --git a/src/iter_trusted_len.rs b/src/iter_trusted_len.rs
new file mode 100644 (file)
index 0000000..aaf8e53
--- /dev/null
@@ -0,0 +1,16 @@
+// Copyright 2017 The UNIC Project Developers.
+//
+// See the COPYRIGHT file at the top-level directory of this distribution.
+//
+// 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::iter;
+
+use super::iter::CharIter;
+
+#[allow(unsafe_code)]
+unsafe impl iter::TrustedLen for CharIter {}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644 (file)
index 0000000..1f6e82c
--- /dev/null
@@ -0,0 +1,75 @@
+// Copyright 2017 The UNIC Project Developers.
+//
+// See the COPYRIGHT file at the top-level directory of this distribution.
+//
+// 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.
+
+#![cfg_attr(not(feature = "std"), no_std)]
+#![cfg_attr(feature = "exact-size-is-empty", feature(exact_size_is_empty))]
+#![cfg_attr(feature = "trusted-len", feature(trusted_len))]
+#![warn(
+    bad_style,
+    missing_debug_implementations,
+    missing_docs,
+    unconditional_recursion
+)]
+#![deny(unsafe_code)]
+
+//! # UNIC — Unicode Character Tools — Character Range
+//!
+//! A simple way to control iteration over a range of characters.
+//!
+//! # Examples
+//!
+//! ```
+//! #[macro_use] extern crate unic_char_range;
+//!
+//! # fn main() {
+//! for character in chars!('a'..='z') {
+//!     // character is each character in the lowercase english alphabet in order
+//! }
+//!
+//! for character in chars!(..) {
+//!     // character is every valid char from lowest codepoint to highest
+//! }
+//! # }
+//! ```
+//!
+//! # Features
+//!
+//! None of these features are included by default; they rely on unstable Rust feature gates.
+//!
+//! - `unstable`: enables all features
+//! - `exact-size-is-empty`: provide a specific impl of [`ExactSizeIterator::is_empty`][is_empty]
+//! - `trusted-len`: impl the [`TrustedLen`] contract
+//!
+//! [is_empty]: https://doc.rust-lang.org/std/iter/trait.ExactSizeIterator.html#method.is_empty
+//! [`FusedIterator`]: https://doc.rust-lang.org/std/iter/trait.FusedIterator.html
+//! [`TrustedLen`]: https://doc.rust-lang.org/std/iter/trait.TrustedLen.html
+//!
+
+mod pkg_info;
+pub use crate::pkg_info::{PKG_DESCRIPTION, PKG_NAME, PKG_VERSION};
+
+mod iter;
+pub use crate::iter::CharIter;
+
+mod range;
+pub use crate::range::CharRange;
+
+#[macro_use]
+mod macros;
+
+mod step;
+
+mod iter_fused;
+
+#[cfg(feature = "trusted-len")]
+mod iter_trusted_len;
+
+#[cfg(feature = "rayon")]
+mod par_iter;
diff --git a/src/macros.rs b/src/macros.rs
new file mode 100644 (file)
index 0000000..a1b41d2
--- /dev/null
@@ -0,0 +1,44 @@
+// Copyright 2017 The UNIC Project Developers.
+//
+// See the COPYRIGHT file at the top-level directory of this distribution.
+//
+// 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.
+
+#[macro_export]
+/// Convenience macro for the initialization of `CharRange`s.
+///
+/// # Syntax
+///
+/// ```
+/// # #[macro_use] extern crate unic_char_range;
+/// # fn main() {
+/// chars!('a'..'z'); // The half open range including 'a' and excluding 'z'
+/// chars!('a'..='z'); // The closed range including 'a' and including 'z'
+/// chars!(..); // All characters
+/// # }
+/// ```
+///
+/// `chars!('a'..='z')` and `chars!(..)` are constant-time expressions, and can be used
+/// where such are required, such as in the initialization of constant data structures.
+///
+/// NOTE: Because an `expr` capture cannot be followed by a `..`/`..=`, this macro captures token
+/// trees. This means that if you want to pass more than one token, you must parenthesize it (e.g.
+/// `chars!('\0' ..= (char::MAX))`).
+macro_rules! chars {
+    ( $low:tt .. $high:tt ) => {
+        $crate::CharRange::open_right($low, $high)
+    };
+    ( $low:tt ..= $high:tt ) => {
+        $crate::CharRange {
+            low: $low,
+            high: $high,
+        }
+    };
+    ( .. ) => {
+        $crate::CharRange::all()
+    };
+}
diff --git a/src/par_iter.rs b/src/par_iter.rs
new file mode 100644 (file)
index 0000000..5b40d22
--- /dev/null
@@ -0,0 +1,110 @@
+// Copyright 2018 The UNIC Project Developers.
+//
+// See the COPYRIGHT file at the top-level directory of this distribution.
+//
+// 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 rayon;
+
+use self::rayon::iter::plumbing::{Consumer, ProducerCallback, UnindexedConsumer};
+use self::rayon::prelude::*;
+use crate::step::{AFTER_SURROGATE, BEFORE_SURROGATE};
+use crate::CharRange;
+use core::char;
+use core::ops::Range;
+
+const SKIP_LENGTH: u32 =
+    crate::step::AFTER_SURROGATE as u32 - crate::step::BEFORE_SURROGATE as u32 - 1;
+
+#[derive(Clone, Debug)]
+pub struct Iter(rayon::iter::Map<rayon::range::Iter<u32>, fn(u32) -> char>);
+
+impl ParallelIterator for Iter {
+    type Item = char;
+
+    fn drive_unindexed<C>(self, consumer: C) -> C::Result
+    where
+        C: UnindexedConsumer<Self::Item>,
+    {
+        self.0.drive_unindexed(consumer)
+    }
+}
+
+impl IndexedParallelIterator for Iter {
+    fn len(&self) -> usize {
+        self.0.len()
+    }
+
+    fn drive<C: Consumer<Self::Item>>(self, consumer: C) -> C::Result {
+        self.0.drive(consumer)
+    }
+
+    fn with_producer<CB: ProducerCallback<Self::Item>>(self, callback: CB) -> CB::Output {
+        self.0.with_producer(callback)
+    }
+}
+
+impl CharRange {
+    fn compact_range(&self) -> Range<u32> {
+        let low = self.low as u32;
+        let high = self.high as u32 + 1;
+        low..(if self.high >= AFTER_SURROGATE {
+            high - SKIP_LENGTH
+        } else {
+            high
+        })
+    }
+}
+
+impl IntoParallelIterator for CharRange {
+    type Item = char;
+    type Iter = Iter;
+
+    fn into_par_iter(self) -> Self::Iter {
+        Iter(self.compact_range().into_par_iter().map(|c| {
+            let c = if c > BEFORE_SURROGATE as u32 {
+                c + SKIP_LENGTH
+            } else {
+                c
+            };
+            debug_assert!(c <= BEFORE_SURROGATE as u32 || c >= AFTER_SURROGATE as u32);
+            debug_assert!(c <= char::MAX as u32);
+            #[allow(unsafe_code)]
+            unsafe {
+                char::from_u32_unchecked(c)
+            }
+        }))
+    }
+}
+
+impl<'a> IntoParallelIterator for &'a CharRange {
+    type Item = char;
+    type Iter = Iter;
+
+    fn into_par_iter(self) -> Self::Iter {
+        (*self).into_par_iter()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn length_agrees() {
+        assert_eq!(chars!(..).iter().count(), chars!(..).par_iter().count())
+    }
+
+    #[test]
+    #[cfg(feature = "std")]
+    fn content_agrees() {
+        assert_eq!(
+            chars!(..).iter().collect::<Vec<_>>(),
+            chars!(..).par_iter().collect::<Vec<_>>()
+        )
+    }
+}
diff --git a/src/pkg_info.rs b/src/pkg_info.rs
new file mode 100644 (file)
index 0000000..a1ab285
--- /dev/null
@@ -0,0 +1,20 @@
+// Copyright 2017 The UNIC Project Developers.
+//
+// See the COPYRIGHT file at the top-level directory of this distribution.
+//
+// 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.
+
+//! Package information
+
+/// UNIC component version.
+pub const PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
+
+/// UNIC component name.
+pub const PKG_NAME: &str = env!("CARGO_PKG_NAME");
+
+/// UNIC component description.
+pub const PKG_DESCRIPTION: &str = env!("CARGO_PKG_DESCRIPTION");
diff --git a/src/range.rs b/src/range.rs
new file mode 100644 (file)
index 0000000..d958016
--- /dev/null
@@ -0,0 +1,231 @@
+// Copyright 2017 The UNIC Project Developers.
+//
+// See the COPYRIGHT file at the top-level directory of this distribution.
+//
+// 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::{char, cmp};
+
+#[cfg(feature = "std")]
+use std::collections::Bound;
+
+use self::cmp::Ordering;
+use crate::CharIter;
+
+/// A range of unicode code points.
+///
+/// The most idiomatic way to construct this range is through the use of the `chars!` macro:
+///
+/// ```
+/// #[macro_use] extern crate unic_char_range;
+/// use unic_char_range::CharRange;
+///
+/// # fn main() {
+/// assert_eq!(chars!('a'..='z'), CharRange::closed('a', 'z'));
+/// assert_eq!(chars!('a'..'z'), CharRange::open_right('a', 'z'));
+/// assert_eq!(chars!(..), CharRange::all());
+/// # }
+/// ```
+///
+/// If constructed in reverse order, such that `self.high` is ordered before `self.low`,
+/// the range is empty. If you want to iterate in decreasing order, use `.iter().rev()`.
+/// All empty ranges are considered equal no matter the internal state.
+#[derive(Copy, Clone, Debug, Eq)]
+pub struct CharRange {
+    /// The lowest character in this range (inclusive).
+    pub low: char,
+
+    /// The highest character in this range (inclusive).
+    pub high: char,
+}
+
+/// Constructors
+impl CharRange {
+    /// Construct a closed range of characters.
+    ///
+    /// If `stop` is ordered before `start`, the resulting range will be empty.
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// # use unic_char_range::*;
+    /// assert_eq!(
+    ///     CharRange::closed('a', 'd').iter().collect::<Vec<_>>(),
+    ///     vec!['a', 'b', 'c', 'd']
+    /// )
+    /// ```
+    pub fn closed(start: char, stop: char) -> CharRange {
+        CharRange {
+            low: start,
+            high: stop,
+        }
+    }
+
+    /// Construct a half open (right) range of characters.
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// # use unic_char_range::*;
+    /// assert_eq!(
+    ///     CharRange::open_right('a', 'd').iter().collect::<Vec<_>>(),
+    ///     vec!['a', 'b', 'c']
+    /// )
+    /// ```
+    pub fn open_right(start: char, stop: char) -> CharRange {
+        let mut iter = CharRange::closed(start, stop).iter();
+        let _ = iter.next_back();
+        iter.into()
+    }
+
+    /// Construct a half open (left) range of characters.
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// # use unic_char_range::*;
+    /// assert_eq!(
+    ///     CharRange::open_left('a', 'd').iter().collect::<Vec<_>>(),
+    ///     vec!['b', 'c', 'd']
+    /// )
+    /// ```
+    pub fn open_left(start: char, stop: char) -> CharRange {
+        let mut iter = CharRange::closed(start, stop).iter();
+        let _ = iter.next();
+        iter.into()
+    }
+
+    /// Construct a fully open range of characters.
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// # use unic_char_range::*;
+    /// assert_eq!(
+    ///     CharRange::open('a', 'd').iter().collect::<Vec<_>>(),
+    ///     vec!['b', 'c']
+    /// )
+    /// ```
+    pub fn open(start: char, stop: char) -> CharRange {
+        let mut iter = CharRange::closed(start, stop).iter();
+        let _ = iter.next();
+        let _ = iter.next_back();
+        iter.into()
+    }
+
+    #[cfg(feature = "std")]
+    /// Construct a range of characters from bounds.
+    pub fn bound(start: Bound<char>, stop: Bound<char>) -> CharRange {
+        let start = if start == Bound::Unbounded {
+            Bound::Included('\u{0}')
+        } else {
+            start
+        };
+        let stop = if stop == Bound::Unbounded {
+            Bound::Included(char::MAX)
+        } else {
+            stop
+        };
+        match (start, stop) {
+            (Bound::Included(start), Bound::Included(stop)) => CharRange::closed(start, stop),
+            (Bound::Excluded(start), Bound::Excluded(stop)) => CharRange::open(start, stop),
+            (Bound::Included(start), Bound::Excluded(stop)) => CharRange::open_right(start, stop),
+            (Bound::Excluded(start), Bound::Included(stop)) => CharRange::open_left(start, stop),
+            (Bound::Unbounded, _) | (_, Bound::Unbounded) => unreachable!(),
+        }
+    }
+
+    /// Construct a range over all Unicode characters (Unicode Scalar Values).
+    pub fn all() -> CharRange {
+        CharRange::closed('\u{0}', char::MAX)
+    }
+
+    /// Construct a range over all characters of *assigned* Unicode Planes.
+    ///
+    /// Assigned *normal* (non-special) Unicode Planes are:
+    /// - Plane 0: *Basic Multilingual Plane* (BMP)
+    /// - Plane 1: *Supplementary Multilingual Plane* (SMP)
+    /// - Plane 2: *Supplementary Ideographic Plane* (SIP)
+    ///
+    /// Unicode Plane 14, *Supplementary Special-purpose Plane* (SSP), is not included in this
+    /// range, mainly because of the limit of `CharRange` only supporting a continuous range.
+    ///
+    /// Unicode Planes 3 to 13 are *Unassigned* planes and therefore excluded.
+    ///
+    /// Unicode Planes 15 and 16 are *Private Use Area* planes and won't have Unicode-assigned
+    /// characters.
+    pub fn assigned_normal_planes() -> CharRange {
+        CharRange::closed('\u{0}', '\u{2_FFFF}')
+    }
+}
+
+/// Collection-like fns
+impl CharRange {
+    /// Does this range include a character?
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use unic_char_range::CharRange;
+    /// assert!(   CharRange::closed('a', 'g').contains('d'));
+    /// assert!( ! CharRange::closed('a', 'g').contains('z'));
+    ///
+    /// assert!( ! CharRange:: open ('a', 'a').contains('a'));
+    /// assert!( ! CharRange::closed('z', 'a').contains('g'));
+    /// ```
+    pub fn contains(&self, ch: char) -> bool {
+        self.low <= ch && ch <= self.high
+    }
+
+    /// Determine the ordering of this range and a character.
+    ///
+    /// # Panics
+    ///
+    /// Panics if the range is empty. This fn may be adjusted in the future to not panic
+    /// in optimized builds. Even if so, an empty range will never compare as `Ordering::Equal`.
+    pub fn cmp_char(&self, ch: char) -> Ordering {
+        // possible optimization: only assert this in debug builds
+        assert!(!self.is_empty(), "Cannot compare empty range's ordering");
+        if self.high < ch {
+            Ordering::Less
+        } else if self.low > ch {
+            Ordering::Greater
+        } else {
+            Ordering::Equal
+        }
+    }
+
+    /// How many characters are in this range?
+    pub fn len(&self) -> usize {
+        self.iter().len()
+    }
+
+    /// Is this range empty?
+    pub fn is_empty(&self) -> bool {
+        self.low > self.high
+    }
+
+    /// Create an iterator over this range.
+    pub fn iter(&self) -> CharIter {
+        (*self).into()
+    }
+}
+
+impl IntoIterator for CharRange {
+    type IntoIter = CharIter;
+    type Item = char;
+
+    fn into_iter(self) -> CharIter {
+        self.iter()
+    }
+}
+
+impl PartialEq<CharRange> for CharRange {
+    fn eq(&self, other: &CharRange) -> bool {
+        (self.is_empty() && other.is_empty()) || (self.low == other.low && self.high == other.high)
+    }
+}
diff --git a/src/step.rs b/src/step.rs
new file mode 100644 (file)
index 0000000..1140edd
--- /dev/null
@@ -0,0 +1,45 @@
+// Copyright 2017 The UNIC Project Developers.
+//
+// See the COPYRIGHT file at the top-level directory of this distribution.
+//
+// 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::char;
+
+pub const BEFORE_SURROGATE: char = '\u{D7FF}';
+pub const AFTER_SURROGATE: char = '\u{E000}';
+
+#[inline]
+#[allow(unsafe_code)]
+/// Step a character one step towards `char::MAX`.
+///
+/// # Safety
+///
+/// If the given character is `char::MAX`, the return value is not a valid character.
+pub unsafe fn forward(ch: char) -> char {
+    if ch == BEFORE_SURROGATE {
+        AFTER_SURROGATE
+    } else {
+        char::from_u32_unchecked(ch as u32 + 1)
+    }
+}
+
+#[inline]
+#[allow(unsafe_code)]
+/// Step a character one step towards `'\0'`.
+///
+/// # Safety
+///
+/// If the given character is `'\0'`, this will cause an underflow.
+/// (Thus, it will panic in debug mode, undefined behavior in release mode.)
+pub unsafe fn backward(ch: char) -> char {
+    if ch == AFTER_SURROGATE {
+        BEFORE_SURROGATE
+    } else {
+        char::from_u32_unchecked(ch as u32 - 1)
+    }
+}
diff --git a/tests/iter_tests.rs b/tests/iter_tests.rs
new file mode 100644 (file)
index 0000000..9678009
--- /dev/null
@@ -0,0 +1,78 @@
+// Copyright 2017 The UNIC Project Developers.
+//
+// See the COPYRIGHT file at the top-level directory of this distribution.
+//
+// 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::{char, u32, vec};
+
+use unic_char_range::CharRange;
+
+fn all_chars() -> vec::IntoIter<char> {
+    (u32::MIN..u32::MAX)
+        .take_while(|&u| u <= char::MAX as u32)
+        .filter_map(char::from_u32)
+        .collect::<Vec<_>>()
+        .into_iter()
+}
+
+#[test]
+fn test_iter_all_chars() {
+    assert!(CharRange::all().iter().eq(all_chars()))
+}
+
+#[test]
+fn test_iter_all_chars_rev() {
+    assert!(CharRange::all().iter().rev().eq(all_chars().rev()))
+}
+
+#[test]
+fn test_iter_all_chars_mixed_next_back() {
+    let mut custom = CharRange::all().iter();
+    let mut simple = all_chars();
+    while let Some(custom_char) = custom.next() {
+        assert_eq!(Some(custom_char), simple.next());
+        assert_eq!(custom.next_back(), simple.next_back());
+    }
+    assert_eq!(None, simple.next());
+}
+
+#[test]
+fn test_iter_all_chars_into_iter() {
+    for _ch in CharRange::all() {
+        // nothing
+    }
+}
+
+#[test]
+fn test_iter_fused() {
+    let mut iter = CharRange::all().iter();
+    let mut fused = all_chars().fuse();
+    assert!(iter.by_ref().eq(fused.by_ref()));
+    for _ in 0..100 {
+        assert_eq!(iter.next(), fused.next());
+    }
+}
+
+#[test]
+fn test_iter_exact_trusted_len() {
+    fn assert_presents_right_len<I: ExactSizeIterator>(iter: &I, len: usize) {
+        assert_eq!(iter.len(), len);
+        assert_eq!(iter.size_hint(), (len, Some(len)));
+    }
+
+    let mut iter = CharRange::all().iter();
+    let mut predicted_length = iter.len();
+    assert_eq!(predicted_length, all_chars().len());
+
+    while let Some(_) = iter.next() {
+        predicted_length -= 1;
+        assert_presents_right_len(&iter, predicted_length);
+    }
+
+    assert_presents_right_len(&iter, 0);
+}